每一個函數都會建立一個 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.構造函數\原型對象\實例是三個徹底不相同的對象:this
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.同一個構造函數建立的聯賽哥哥實例,共享同一個原型對象。spa
console.log(person1.__proto__===person2.__proto__); // true
7.使用 instanceof 檢查實例的原型鏈中是否包含指定構造函數的原型:prototype
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 看出各個對象之間的關係。指針
圖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,所下所示:code
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.本期的分享到了這裏就結束啦,但願對你有所幫助,讓咱們一塊兒努力走向巔峯!