現(xiàn)在看這張圖開始變得云里霧里,所以簡(jiǎn)單回顧一下 prototype 的基本內(nèi)容,能夠基本讀懂這張圖的脈絡(luò)。
先介紹一個(gè)基本概念:
function Person() {}
Person.prototype.name = 'KK';
let person1 = new Person();
在上面的例子中,
Person 叫做構(gòu)造函數(shù)(函數(shù)被進(jìn)行構(gòu)造調(diào)用,為下文方便,稱之為構(gòu)造函數(shù)
)
Person.prototype 叫做 Person 的原型對(duì)象
person1 又稱之為實(shí)例
。
TL;DR.
構(gòu)造函數(shù)默認(rèn)會(huì)包含一個(gè) prototype 屬性,其值指向原型對(duì)象,即 Parent.prototype = Parent.prototype
默認(rèn)情況下,構(gòu)造函數(shù).原型對(duì)象.constructor = 構(gòu)造函數(shù) Parent.prototype.constructor = Parent 。但如果有明確改寫,則未必是這種指向。
實(shí)例.proto = 構(gòu)造函數(shù).原型對(duì)象 person.proto = Parent.prototype
實(shí)例和構(gòu)造函數(shù)的原型對(duì)象之間有直接聯(lián)系,但是實(shí)例和構(gòu)造函數(shù)之間沒有直接聯(lián)系。
原型對(duì)象
在創(chuàng)建函數(shù)時(shí), Function 的構(gòu)造器產(chǎn)生函數(shù)對(duì)象時(shí)為其綁定的一個(gè)存放繼承特征的對(duì)象屬性prototype 。 Function 的構(gòu)造器產(chǎn)生函數(shù)對(duì)象時(shí),會(huì)運(yùn)行類似的代碼:
this.prototype = { constructor : this}
這個(gè)屬性 prototype 的值是默認(rèn)只會(huì)獲得一個(gè)包含 constructor 屬性的對(duì)象,其余的方法都繼承自 Object。這個(gè)對(duì)象就是通過調(diào)用構(gòu)造函數(shù)創(chuàng)建的對(duì)象的原型。使用原型對(duì)象 prototype 的好處就是,在它上面定義的屬性和方法可以被對(duì)象的實(shí)例所共享。 原本在構(gòu)造函數(shù)中給對(duì)象實(shí)例賦的值,可以直接賦值給它們的原型。
function Parent() {}
// 定義在 Parent.prototype 的值,可以被該對(duì)象實(shí)例共享。
Parent.prototype.name = 'KK';
Parent.prototype.sayHi = function () {
console.log('hi,', this.name);
};
// Parent 對(duì)象的實(shí)例 person1, person2
let person1 = new Parent();
let person2 = new Parent();
person1.sayHi(); // hi, KK
person2.sayHi(); // hi, KK
而 prototype 里包含的 constructor 指向了與之關(guān)聯(lián)的構(gòu)造函數(shù)。如下圖所示,我們可以看到 Parent 的 prototype 指向了 Parent.prototype (看起來是句廢話),而 Parent.prototype 內(nèi)的 constructor 指向了 Parent 。換言之,構(gòu)造函數(shù)和構(gòu)造函數(shù)原型對(duì)象的 constructor 之間形成了循環(huán)引用。
proto | [[prototype]]
開始之前,讓我們來回顧一下 new 操作符, Object.create() 方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象的 proto。 通過這一步,實(shí)現(xiàn)了實(shí)例對(duì)象和構(gòu)造函數(shù) func 的原型對(duì)象進(jìn)行鏈接,從而方便訪問定義在原型對(duì)象上的方法或者屬性。
function myNew(func, ...args) {
// 1. 在內(nèi)存中創(chuàng)建一個(gè)新對(duì)象
const obj = Object.create()
// 2. 在這個(gè)新對(duì)象內(nèi)部的 [[ prototype ]] 特性被賦值為構(gòu)造函數(shù)的 prototype 屬性
obj.__proto__ = func.prototype
// 3. 構(gòu)造函數(shù)內(nèi)部的 this 被賦值為這個(gè)新對(duì)象
// 執(zhí)行構(gòu)造函數(shù)內(nèi)部代碼
let result = func.apply(obj, args)
// 4. 如果構(gòu)造函數(shù)返回一個(gè)非空對(duì)象,則返回該對(duì)象,都則則返回剛剛創(chuàng)建的新對(duì)象
return result instanceof Object ? result : obj
}
其中,第1.2步,可以合并起來變成 const obj = Object.create(func.prototype) , Object.create() 方法會(huì)創(chuàng)建一個(gè)新對(duì)象,并把新對(duì)象的 proto 關(guān)聯(lián)到指定的對(duì)象上。
回顧完 new 操作符,接著我們剛剛的例子, person1 和 person2 ,它們是由 Parent 創(chuàng)建的對(duì)象實(shí)例。每一次我們通過構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例的時(shí)候,實(shí)例會(huì)通過 proto 鏈接到構(gòu)造函數(shù)的原型對(duì)象上面,及下圖的第一個(gè)部分。
function Parent() {}
// Parent 對(duì)象的實(shí)例 person1, person2
let person1 = new Parent();
let person2 = new Parent();
但是,我們從圖中可以看到,對(duì)象實(shí)例 person 的 proto 直接指向了其構(gòu)造函數(shù)的原型對(duì)象 Parent.prototype ,實(shí)例和原型對(duì)象之間,其實(shí)是一個(gè)引用的關(guān)系,并不是重新保存了一個(gè)原型對(duì)象的副本。同時(shí),我們也可以看到,對(duì)象實(shí)例和構(gòu)造函數(shù)并沒有發(fā)生直接的聯(lián)系。
console.log(person1.__proto__ === Parent.prototype); // true
console.log(person1.__proto__.constructor === Parent); // true
console.log(person1 instanceof Parent)
原型鏈查找
我們經(jīng)常會(huì)提起一個(gè)沿著原型鏈進(jìn)行查找,但是怎么算是原型鏈查找呢?如何進(jìn)行原型鏈查找呢?
對(duì)于對(duì)象默認(rèn)的 get 操作,會(huì)先開始從對(duì)象實(shí)例開始尋找,如果發(fā)現(xiàn)則返回給定的對(duì)象屬性值,否則搜索將進(jìn)入原型對(duì)象,在原型對(duì)象上開始尋找,再返回對(duì)應(yīng)的值。如上一節(jié)的圖示,我們可以看到當(dāng)我們輸入 person.name 那么他首先會(huì)進(jìn)入 person 中,進(jìn)行尋找。當(dāng)尋找無果后,再進(jìn)入 Parent.prototype 進(jìn)行尋找。這也就是原型可以再多個(gè)對(duì)象實(shí)例間共享屬性和方法的原理了。
function Parent() {
name: 'Parent';
}
Parent.prototype.nickname = 'KK';
Parent.prototype.sayHi = function () {
console.log(this.nickname);
};
let person1 = new Parent();
person1.nickname = 'person1';
person1.sayHi();
console.log(person1.nickname); // person1
如果在實(shí)例對(duì)象上添加一個(gè)和原型對(duì)象同名的屬性,那么就會(huì)在實(shí)例上創(chuàng)建這個(gè)屬性值,這個(gè)在實(shí)例對(duì)象上的同名屬性就會(huì)遮蔽原型對(duì)象上原有的屬性。所謂遮蔽,只是因?yàn)闀?huì)屏蔽對(duì)原型對(duì)象上同名屬性的訪問,但是并不會(huì)修改。只有使用 delete 才能完全刪除實(shí)例上的這個(gè)屬性,恢復(fù)對(duì)原型對(duì)象同名屬性的訪問。 否則,即使將實(shí)例上的同名屬性修改為 null ,也無法恢復(fù)它和原型對(duì)象同名屬性的聯(lián)系。
屬性’覆寫’ or 遮蔽 Shadow
當(dāng)然,這個(gè)對(duì)象屬性的設(shè)置也有一定的規(guī)則,所謂的屏蔽也比我們想象的復(fù)雜。 我們可以來分析一下在實(shí)例對(duì)象上進(jìn)行同名屬性 set 動(dòng)作的過程,這個(gè)會(huì)分為幾個(gè)情況:
當(dāng)原型對(duì)象上,不存在該屬性時(shí),直接設(shè)置即可;
當(dāng)原型對(duì)象上,存在該屬性:
僅為普通數(shù)據(jù)訪問屬性,且沒有設(shè)置 writable:false ,則會(huì)在實(shí)例對(duì)象上,添加一個(gè)同名屬性,遮蔽對(duì)原來原型對(duì)象上同名屬性的訪問;
當(dāng)設(shè)置了 writable:false,標(biāo)記該屬性為只讀屬性(read-only),那么給實(shí)例對(duì)象設(shè)置同名屬性的操作將會(huì)被攔截,這個(gè)復(fù)制操作在嚴(yán)格模式會(huì)報(bào)錯(cuò),在非嚴(yán)格模式下會(huì)默認(rèn)忽略??傃灾?,無法對(duì)原型對(duì)象的同名屬性產(chǎn)生遮蔽的效果。
Attention: 看起來這個(gè)只要原型對(duì)象上存在 read-only 的屬性,則無法進(jìn)行同名屬性賦值有點(diǎn)令人疑惑。但是這個(gè)限制只存在于 = 的復(fù)制操作中,如果直接使用 Object.defineProperty() 則不會(huì)受到影響,還是可以直接給實(shí)例對(duì)象進(jìn)行復(fù)制操作。
如果原型對(duì)象的同名屬性被設(shè)置了 setter , 那么這個(gè) setter 會(huì)被調(diào)用,這個(gè)設(shè)置同名屬性的動(dòng)作會(huì)做用于這個(gè) setter ,同樣無法對(duì)原型對(duì)象的同名屬性產(chǎn)生遮蔽的效果。
in 和 hasOwnProperty 屬性來源
假定我們有這么一段代碼,我們可以看到我們通過普通的復(fù)制方式,遮蔽了 person 原型對(duì)象上的 nickname 屬性。不管這個(gè)屬性是在實(shí)例上還是在原型上,只要在對(duì)象屬性可以通過對(duì)象訪問時(shí),使用 in 操作符,操作結(jié)果都會(huì)返回 true。
function Parent() {
nickname: 'Parent';
}
Parent.prototype.nickname = 'KK';
let person1 = new Parent();
let person2 = new Parent();
person1.nickname = 'person1';
console.log('nickname' in person1); // true
console.log('nickname' in person2); // true
我們知道,對(duì)于屬性訪問,即對(duì)屬性的 get 操作,是會(huì)沿著原型鏈上進(jìn)行查找,直至找到或者到根對(duì)象上。所以,如果需要判斷這個(gè)屬性到底是在實(shí)例對(duì)象上還是原型對(duì)象上,可使用 hasOwnProperty() 方法,這個(gè)方法可以用來確定某個(gè)屬性在實(shí)例上還是在原型對(duì)象上,當(dāng)且僅當(dāng)這個(gè)屬性是在調(diào)用它的對(duì)象上時(shí),返回 true ,如:
console.log(person1.hasOwnProperty('nickname')); // true
console.log(person2.hasOwnProperty('nickname')); // false
結(jié)合上面提到的 in 和 hasOwnProperty ,那么我們只想要原型對(duì)象上的屬性又要怎么進(jìn)行判斷呢? 可以創(chuàng)造一個(gè) hasPrototypeProperty 方法,結(jié)合 in 和 hasOwnProperty 各自的特點(diǎn):
function hasPrototypeProperty(obj, propertyKey) {
return !obj.hasOwnProperty(propertyKey) && propertyKey in obj;
}
屬性獲取
當(dāng)涉及到了遍歷,在對(duì)象屬性遍歷中,我們可以用 for-in , Object.keys() ,單這二者也是有一定的區(qū)別的。
for-in
在 for 里面使用 in 操作符,可通過對(duì)象訪問且可枚舉的屬性都會(huì)被返回,包括實(shí)例屬性以及原型對(duì)象上的屬性。當(dāng)我們?cè)O(shè)置了 Enumberable:false 時(shí),那么這個(gè)對(duì)象就變成了一個(gè)不可枚舉屬性,他才不會(huì)在循環(huán)中返回。需要注意一點(diǎn),當(dāng)我們?cè)趯?shí)例上定義一個(gè)同名屬性去遮蔽原型對(duì)象一個(gè)不可枚舉的屬性時(shí),這個(gè)在實(shí)例對(duì)象上沒有被顯式定義為不可枚舉的同名屬性,可以被返回,即不可枚舉屬性不會(huì)影響到實(shí)例屬性。
Object.keys()
若想要獲得對(duì)象上所有可枚舉屬性時(shí),可以用 Object.keys() 的方法,這個(gè)方法接受對(duì)象作為參數(shù),返回該對(duì)象上所有可枚舉屬性名數(shù)組,而不會(huì)包含其原型對(duì)象上屬性。
可以把這個(gè)方法理解為 for-in + hasOwnProperty 的結(jié)合,只返回在對(duì)象上的屬性,而不會(huì)沿著原型鏈進(jìn)行查找。
Object.getOwnPropertyNames()
如果想列出所有實(shí)例屬性,無論是否可以枚舉,可以使用Object.getOwnPropertyNames()。但是需注意,這個(gè)方法的返回結(jié)果會(huì)包含了不可枚舉屬性。當(dāng)我們對(duì)原型對(duì)象使用這個(gè)方法時(shí),該方法會(huì)把 constructor 給返回回來。
在 ES6 出現(xiàn) Symbol 的符號(hào)類型之后,相應(yīng)也會(huì)出現(xiàn)一個(gè) Object.getOwnPropertySymbols(),這個(gè)方法其實(shí)與 getOwnPropertyNames 類似,只是這個(gè)方法針對(duì) Symbol 這種符號(hào)類型而已。
對(duì)于這幾個(gè)方法,for-in , Object.keys() 的枚舉順序返回是不確定的,具體實(shí)現(xiàn)是取決于對(duì)應(yīng)的 JavaScript 引擎的實(shí)現(xiàn),而用 Object.getOwnPropertyNames() or Object.assign() 則是確定的,結(jié)果的返回會(huì)依據(jù)枚舉數(shù)值鍵-插入的順序枚舉字符串-符號(hào)鍵。在 YDKJS 里有例子,我稍微拓展一下,可能看得更清晰一些:
let sym1 = Symbol('sym1')
let sym2 = Symbol('sym2')
let sym3 = Symbol('sym3')
let obj = {
1: 1,
[sym3]: 'sym3',
[sym1]: 'sym1',
second: 'second',
first: 'first',
0: 0
}
obj[sym2] = 'sym2'
obj[3] = 3
obj.forth = 'forth'
obj.third = 'third'
obj[2] = 2
console.log(Object.getOwnPropertyNames(obj)) // [ '0', '1', '2', '3', 'second', 'first', 'forth', 'third' ]
console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol(sym3), Symbol(sym1), Symbol(sym2) ]
prototype 判斷方法
Object.getPrototypeOf()
Object.getPrototypeOf() ****方法返回指定對(duì)象的原型(內(nèi)部 [[Prototype]] 屬性的值)。具體用法為:
Object.getPrototypeOf(object)
Object.getPrototypeOf(person1); // { name: 'KK', sayHi: [Function (anonymous)] }
Object.getPrototypeOf(person1) === Parent; // true
Object.prototype.isPrototypeOf()
Object.prototype.isPrototypeOf() 方法用于測(cè)試一個(gè)對(duì)象是否存在于另一個(gè)對(duì)象的原型鏈上。雖然并不是所有的方法都實(shí)現(xiàn)了對(duì)外暴露 proto ,但是我們可以通過 isPrototypeOf() 來確定 Parent.prototype 和 person 之間的關(guān)系,而因?yàn)?person 中有鏈接指向了 Person.prototype 。 所以結(jié)果返回為 true 。
Parent.prototype.isPrototypeOf(person1); // true
instanceof
instanceof 用于實(shí)例的原型鏈上是否包含了某個(gè)構(gòu)造函數(shù)的 prototype 。具體用法為, instance instanceof constructionFunc 。 但是,注意,當(dāng)我們顯式改變了某個(gè)實(shí)例的 prototype 時(shí),這個(gè)方法恐不適用。文章來源:http://www.zghlxwxcb.cn/news/detail-693854.html
person1 instanceof Parent ; // true
本文簡(jiǎn)單介紹了關(guān)于 prototype 的一些相關(guān)內(nèi)容,對(duì)于文章開頭的圖片,現(xiàn)在我們回過頭來看,雖然還是覺得反應(yīng)會(huì)比較慢,但是仔細(xì)思考之后應(yīng)該可以看懂整張圖的鏈路和脈絡(luò)。 簡(jiǎn)單休息一下,好好消化一下內(nèi)容,接下來就要開始新的文章章節(jié),原型鏈繼承。文章來源地址http://www.zghlxwxcb.cn/news/detail-693854.html
到了這里,關(guān)于es5的實(shí)例__proto__(原型鏈) prototype(原型對(duì)象) {constructor:構(gòu)造函數(shù)}的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!