首頁>技術>

1、工廠模式

new Object()

function createPerson(name, age, job) {    let o = new Object();    o.name = name;    o.age = age;    o.job = job;    o.sayName = function () {        console.log(this.name);    };    return o;}let person1 = createPerson("Nicholas", 29, "Software Engineer");let person2 = createPerson("Greg", 27, "Doctor");
這裡,函式 createPerson() 接收3個引數,根據這幾個引數構建了一個包含 Person 資訊的物件。可以用不同的引數多次呼叫這個函式,每次都會返回包含3個屬性和1個方法的物件。這種工廠模式雖然可以解決建立多個類似物件的問題,但沒有解決物件標識問題(即新建立的物件是什麼型別)。2、建構函式模式
function Person(name, age, job) {    this.name = name;    this.age = age;    this.job = job;    this.sayName = function () {        console.log(this.name);    };}let person1 = new Person("Nicholas", 29, "Software Engineer");let person2 = new Person("Greg", 27, "Doctor");person1.sayName(); // Nicholas person2.sayName(); // Greg
這種替代了工廠函式,實現效果一樣的,區別在於:沒有顯式地建立物件。屬性和方法直接賦值給了 this 。沒有 return 。Person 的首字母要大寫 這是慣例,建構函式名稱的首字母都是要大寫的,非建構函式則以小寫字母開頭let person1 = new Person("Nicholas", 29, "Software Engineer");執行過程:

(1) 在記憶體中建立一個新物件。 (2) 這個新物件內部的 [[Prototype]] 特性被賦值為建構函式的 prototype 屬性。即為person1.__proto__===Person.prototypeperson1.constructor == Person (3) 建構函式內部的 this 被賦值為這個新物件(即 this 指向新物件)。 (4) 執行建構函式內部的程式碼(給新物件新增屬性)。 (5) 如果建構函式返回非空物件,則返回該物件;否則,返回剛建立的新物件。

constructor 本來是用於標識物件型別的。不過,一般認為 instanceof 運算子是確定物件型別更可靠的方式。

console.log(person1 instanceof Object); // true console.log(person1 instanceof Person); // trueconsole.log(person2 instanceof Object); // true console.log(person2 instanceof Person); // true
建構函式不一定要寫成函式宣告的形式。賦值給變數的函式表示式也可以表示建構函式:
let person1 = new Person("Nicholas", 29, "Software Engineer");let person2 = new Person("Greg", 27, "Doctor");person1.sayName(); // Nicholas person2.sayName(); // Greg console.log(person1 instanceof Object); // true console.log(person1 instanceof Person); // trueconsole.log(person2 instanceof Object); // true console.log(person2 instanceof Person); // true
(1)建構函式也是函式

建構函式與普通函式唯一的區別就是呼叫方式不同,任何函式只要使用 new 運算子呼叫就是建構函式,而不使用 new 運算子呼叫的函式就是普通函式。 比如,前面的例子中定義的 Person() 可以像下面這樣呼叫:

// 作為建構函式let person = new Person("Nicholas", 29, "Software Engineer");person.sayName(); // "Nicholas" // 作為函式呼叫Person("Greg", 27, "Doctor"); // 新增到window物件// 結果會將屬性和方法新增到 window 物件。window.sayName(); // "Greg" // 在另一個物件的作用域中呼叫let o = new Object();Person.call(o, "Kristen", 25, "Nurse");o.sayName(); // "Kristen"
(2)建構函式的問題
function Person(name, age, job) {    this.name = name;    this.age = age;    this.job = job;    this.sayName = sayName;}function sayName() {    console.log(this.name);}let person1 = new Person("Nicholas", 29, "Software Engineer");let person2 = new Person("Greg", 27, "Doctor");person1.sayName(); // Nicholas person2.sayName(); // Greg

sayName() 被定義在了建構函式外部,sayName 屬性等於全域性 sayName() 函式。因為這一次sayName 屬性中包含的只是一個指向外部函式的指標,所以person1 和 person2 共享了定義在全域性作用域上的sayName() 函式。這樣雖然解決了相同邏輯的函式重複定義的問題,但全域性作用域也因此被搞亂了,因為那個函式實際上只能在一個物件上呼叫。如果這個物件需要多個方法,那麼就要在全域性作用域中定義多個函式。這會導致自定義型別引用的程式碼不能很好地聚集一起。這個新問題可以透過原型模式來解決。

3、原型模式

每個函式都會建立一個 prototype 屬性,這個屬性是一個物件,包含應該由特定引用型別的例項共享的屬性和方法。實際上,這個物件就是透過呼叫建構函式建立的物件的原型。使用原型物件的好處是,在它上面定義的屬性和方法可以被物件例項共享。

function Person() {}// let Person = function() {}; 使用函式表示式也可以Person.prototype.name = "Nicholas";Person.prototype.age = 29;Person.prototype.job = "Software Engineer";Person.prototype.sayName = function () {    console.log(this.name);};let person1 = new Person();person1.sayName(); // "Nicholas"let person2 = new Person();person2.sayName(); // "Nicholas"console.log(person1.sayName == person2.sayName); // true

這裡,所有屬性和 sayName() 方法都直接新增到了 Person 的 prototype 屬性上,建構函式體中什麼也沒有。但這樣定義之後,呼叫建構函式建立的新物件仍然擁有相應的屬性和方法。與建構函式模式不同,使用這種原型模式定義的屬性和方法是由所有例項共享的。因此 person1 和 person2 訪問的都是相同的屬性和相同的sayName() 函式。要理解這個過程,就必須理解ECMAScript中原型的本質。

理解原型

無論何時,只要建立一個函式,就會按照特定的規則為這個函式建立一個 prototype 屬性(指向原型物件)。預設情況下,所有原型物件自動獲得一個名為 constructor 的屬性,指回與之關聯的建構函式。對前面的例子而言,Person.prototype.constructor 指向 Person 。然後,因建構函式而異,可能會給原型物件新增其他屬性和方法。在自定義建構函式時,原型物件預設只會獲得 constructor 屬性,其他的所有方法都繼承自 Object 。每次呼叫建構函式建立一個新例項,這個例項的內部 [[Prototype]] 指標就會被賦值為建構函式的原型物件。指令碼中沒有訪問這個[[Prototype]] 特性的標準方式,但Firefox、Safari和Chrome會在每個物件上暴露 proto 屬性,透過這個屬性可以訪問物件的原型。在其他實現中,這個特性完全被隱藏了。關鍵在於理解這一點:例項與建構函式原型之間有直接的聯絡,但例項與建構函式之間沒有。這種關係不好視覺化,但可以透過下面的程式碼來理解原型的行為:

/** * * 建構函式可以是函式表示式 * 也可以是函式宣告,因此以下兩種形式都可以: * function Person {} * let Person = function() {} */function Person() {}/** * 宣告之後,建構函式就有了一個 * 與之關聯的原型物件: */ console.log(typeof Person.prototype);console.log(Person.prototype); // { // constructor: f Person(),// __proto__: Object // }/** *  如前所述,建構函式有一個prototype屬性 * 引用其原型物件,而這個原型物件也有一個 * constructor屬性,引用這個建構函式 * 換句話說,兩者迴圈引用: */console.log(Person.prototype.constructor === Person); // true/** * 正常的原型鏈都會終止於Object的原型物件 * Object原型的原型是null  */console.log(Person.prototype.__proto__ === Object.prototype); // trueconsole.log(Person.prototype.__proto__.constructor === Object); // trueconsole.log(Person.prototype.__proto__.__proto__ === null); // trueconsole.log(Person.prototype.__proto__);// {// constructor: f Object(),// toString: ...// hasOwnProperty: ...// isPrototypeOf: ...// ...// }let person1 = new Person(), person2 = new Person();/*** 建構函式、原型物件和例項*是3個完全不同的物件:*/ console.log(person1 !== Person); // trueconsole.log(person1 !== Person.prototype); // trueconsole.log(Person.prototype !== Person); // true/*** 例項透過__proto__連結到原型物件, * 它實際上指向藏特性[[Prototype]] ** 建構函式透過prototype屬性連結到原型物件 ** 例項與建構函式沒有直接聯絡,與原型物件有直接聯絡 */console.log(person1.__proto__ === Person.prototype); // trueconsole.log(person1.__proto__.constructor === Person); // true/** * 同一個建構函式建立的兩個例項 * 共享同一個原型物件: */console.log(person1.__proto__ === person2.__proto__); // true/** * instanceof檢查例項的原型鏈中 * 是否包含指定建構函式的原型: */ console.log(person1 instanceof Person); // trueconsole.log(person1 instanceof Object);// trueconsole.log(Person.prototype instanceof Object); // true

更多見《JavaScript高階程式設計》3版第六章 或第四版第8章

14
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 又一里程碑!華為頂級工程師總結的網路協議核心手冊首次線上曝光