博客 / 詳情

返回

原型模式

每個函數都會創建一個 prototype 屬性,這個屬性是一個對象,包含應該由特定引用類型的實例共享的屬性和方法。實際上,這個對象就是通過調用構造函數創建的對象的原型。使用原型對象的好處是,在它上面定義大的屬性和方法可以被對象實例共享。

1.原來在構造函數中直接賦給對象實例的值,可以直接賦值給它們的原型,如下所示:

function Person(){}
Person.prototype.name="張三";
Person.prototype.age=29;
Person.prototype.job="Web前端開發";
Person.prototype.sayName=function(){
    console.log(this.name);
}
let person1=new Person();
person1.sayName();// 張三

let person2=new Person();
person2.sayName();// 張三

console.log(person1.sayName===person2.sayName);// true
使用函數表達式也可以:
let Person=function(){};

Person.prototype.name="李四";
Person.prototype.age=20;
Person.prototype.job="IOS開發";
Person.prototype.sayName=function(){
    console.log(this.name);
}

let person1=new Person();
person1.sayName(); //李四

let person2=new Person();
person2.sayName(); //李四

console.log(person1.sayName===person2.sayName);// true
這裏,所有屬性和sayName()方法都直接添加到了 Person 的prototype屬性上,構造函數體中什麼都沒有。但這樣定義之後,調用構造函數創建的新對象仍然擁有相應的屬性和方法。與構造函數模式不同,使用這種原型模式定義的屬性和方法是由所有實例共享的。因此 person1和person2訪問的都是相同的屬性和相同的 sayName()函數。要理解這個過程,就必須理解 ECMAScript中原型的本質。

2.理解原型

無論何時,只要創建一個函數,就會按照特定的規則為這個函數創建一個 prototype屬性(指向原型對象)。默認情況下,所有原型對象自動獲得一個名為 constructor 的屬性,指回與之關聯的構造函數。對前面的例子而言,Person.prototype.constructor 指向Person。然後,因構造函數而異,可能會給原型對象添加其他屬性和方法。
在自定義構造函數時,原型對象默認只會獲得 constructor 屬性,其他的所有方法都繼承自 Object。每次調用構造函數創建一個新實例,這個實例的內部 [[Prototype]]指針就會被賦值為構造函數的原型對象。腳本中沒有訪問這個[[Prototype]]特性的標準方式,但 Firefox\Safari和Chrome會在每個對象上暴漏 proto 屬性,通過這個屬性可以訪問對象的原型。在其他實現中,這個特性完全被隱藏了。關鍵在於理解這一點:實例與構造函數原型之間由直接的聯繫,但實例與構造函數之間沒有。

3.這種關係不好可視化,但可以通過下面的代碼來理解原型的行為:

構造函數可以是函數表達式
也可以是函數聲明,因此以下兩種形式都可以:
/*
   function Person(){}
   let Person=function(){}
*/
function Person(){}
/*
  聲明之後,構造函數就有了一個與之關聯的原型對象:
*/

/*
   如前所述,構造函數有一個 prototype 屬性
   引用其原型對象,而這個原型對象也有一個
   constructor 屬性,引用這個構造函數
   換句話説,兩者循環引用:
   console.log(Person.prototype.constrctor===Person); // true
*/

4.正常的原型鏈都會終止於 Object 的原型對象;Object 原型的原型是 null。

console.log(Person.prototype.__proto__===Object.prototype);  // true
console.log(Person.prototype.__proto__.constructor===Object); // true
console.log(Person.prototype.__proto__.__proto__);  // null

5.構造函數\原型對象\實例是三個完全不相同的對象:

 let person1=new Person(),
     person2=new Person();
   console.log(person1 !=Person); // true
   console.log(person1 !=Person.ptototype);  // true
   console.log(Person.prototype !=Person); // true
1.實例通過 __proto__鏈接到原型對象,它實際上指向隱藏特性[[Prototype]]
2.構造函數通過 prototype 屬性鏈接到原型對象
3.實例與構造函數沒有直接聯繫,與原型對象有直接聯繫
console.log(person1.__proto__===Person.prototype); // true
console.log(person1.__prototype__.constructor===Person); // true

6.同一個構造函數創建的聯賽哥哥實例,共享同一個原型對象。

console.log(person1.__proto__===person2.__proto__); // true

7.使用 instanceof 檢查實例的原型鏈中是否包含指定構造函數的原型:

console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true
console.log(Person.prototype instanceof Object); //true

8.對於前面例子中的 Person 構造函數和 Person.prototype,可以通過圖 8-1 看出各個對象之間的關係。
image.png

圖8-1展示了Person構造函數\Person的原型對象和Person現有兩個實例之間的關係。注意,Person.prototype指向原型對象,而Person.prototype.constrctor指回Person構造函數。原型對象包含 constructor 屬性和其他後來添加的屬性。Person的兩個實例 person1和 person2都只有一個內部屬性指會 Person.prototype,而且兩者都與構造函數沒有直接聯繫。另外要注意,雖然這兩個實例都沒有屬性和方法,但 person1.sayName()可以正常調用。這是由於對象屬性查找機制的原因。

9.雖然不是所有實現都對外暴露了 [[Prototype]],但可以使用 isPrototypeof()方法確定兩個對象之間的這種關係。本質上,isPrototypeof()會在傳入參數的 [[Prototype]]指向調用它的對象時返回 true,所下所示:

console.log(Person.prototype.isPrototype(person1)); // true
console.log(Proson.prototype.isPrototype(person2)); // true
這裏通過原型對象調用 isPrototypeof()方法檢查了 person1和person2。因為這兩個例子內部都有鏈接指向 Person.prototype,所以結果都返回 true。

10.ECMAScript的Object類型有一個方法叫 Object.getPrototypeof(),返回參數的內部特性 [[Prototype]]的值。例如:

console.log(Object.getPrototypeof(person1)==Person.prototype); //true
console.log(Object.getPrototypeof(person1).name);// 李四
第一行代碼簡單確認了 Object.getPrototypeof()返回的對象就是傳入對象的原型對象。第二行代碼取得了原型對象上的 name 屬性的值,即 "李四"。使用 Object.getPrototypeof()可以方便地取得一個對象的原型,而這在通過原型實現繼承時顯得尤為重要。

11.Object類型還有一個 setPrototypeof()方法,可以向實例的私有特性 [[Prototypeo]]寫入一個新值。這樣就可以重寫一個對象的原型繼承關係:

let biped={
    numLegs:2
};
let person={
    name:"Chen"
}
Object.setPrototypeof(person,biped);

console.log(person.name); // Chen
console.log(person.numLegs); // 2
console.log(Object.getPrototypeof(person)===biped); //true
警告:Object.setPrototypeof()可能會嚴重影響代碼性能。Mozilla文檔説得很清楚:"在所有瀏覽器和JvavaScript引擎中,修改繼承關係的影響都是微妙且深遠的。這種影響且不僅時執行 Object.setPrototypeof()語那麼簡單,而且會涉及所有訪問了那些修改過 [[Prototype]]"的對象的代碼。

12.為避免使用 Object.setPrototypeof()可能造成的性能下降,可以通過 Object.create()來創建一個新對象,同時為其指定原型:

let biped={
    numLegs:2
}
let person=Object.create(biped);
person.name="Chen";

console.log(person.name); // Chen
console.log(person.numLegs); // 2
console.log(Object.getPrototypeof(person)===biped); // true

13.本期的分享到了這裏就結束啦,希望對你有所幫助,讓我們一起努力走向巔峯!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.