說到原型和原型鏈可能對于每個js初學(xué)者都是一件非常頭疼的事岸浑,因為它們之間的關(guān)系太繞了,網(wǎng)上的相關(guān)文章也是不計其數(shù)型将。本文將從另一個角度去理解什么是原型和原型鏈够挂。
我們先認(rèn)識一下什么是實例和構(gòu)造函數(shù)吧。
構(gòu)造函數(shù)創(chuàng)建對象
我們先使用構(gòu)造函數(shù)創(chuàng)建一個對象:
// 構(gòu)造函數(shù)使用的是大駝峰式寫法(首字母大寫)
function Person() {
};
var person = new Person();
person.name = 'charming';
console.log(person.name);
在這個例子中Person就是構(gòu)造函數(shù)贷掖,而聲明的這個person就是對象實例嫡秕。
接下來就進(jìn)入主題:
prototype
每個函數(shù)都有prototype屬性,我們會在各種例子上見到它的身影苹威,比如:
function Person() {
};
//需要注意的是只有函數(shù)才有prototype屬性
Person.prototype.name = 'charming';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name); // 'charming'
console.log(person2.name); // 'charming'
那函數(shù)的prototype屬性到底指向誰呢昆咽?是這個函數(shù)的原型嗎?
其實牙甫,這個函數(shù)的prototype指向的是一個對象掷酗,這個對象正是調(diào)用該構(gòu)造函數(shù)創(chuàng)建的實例的原型,也就是這個例子中的person1和person2的原型窟哺。
那原型是什么呢汇在?我們這樣理解:
每個JavaScript對象(null除外)創(chuàng)建的時候會與之關(guān)聯(lián)另一個對象,這個對象就是我們所說的******脏答,每個對象都會從原型上”繼承“屬性糕殉。
讓我們用一張圖來表示構(gòu)造函數(shù)和實例原型之間的關(guān)系:
在這張圖中我們用Object.prototype來表示實例原型。
那我們該怎么表示實例和實例原型殖告,也就是person和Person.prototype之間的關(guān)系呢阿蝶,這就用到我們要講的的第二個屬性了:
proto
每個JavaScript對象(null除外)都具有的一個屬性,叫_proto_
這個屬性會指向該對象的原型黄绩。
為了證明這一點我們可以在火狐或者谷歌中測試一下
function Person() {
};
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
現(xiàn)在我們再來更新一下關(guān)系:
既然實例和構(gòu)造函數(shù)都可以指向原型羡洁,那原型是否有屬性指向構(gòu)造函數(shù)或者實例呢?
constructor
指向?qū)嵗故菦]有爽丹,因為一個構(gòu)造函數(shù)可以生成多個實例筑煮,但是原型指向構(gòu)造函數(shù)倒是可以,這就是我們要說的第三個屬性:constructor粤蝎,每個對象都有一個 constructor 屬性指向關(guān)聯(lián)的構(gòu)造函數(shù)真仲。
為了驗證這一點我們可以嘗試一下:
function Person() {
};
console.log(Person === Person.prototype.constructor) // true
所以我們就再更新一下關(guān)系圖:
綜上我們得出:
function Person() {
};
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person) // true
// 順便用一個es5的方法,用于獲取對象原型
console.log(Object.getPrototypeOf(person) === Person.prototype); // true
了解了構(gòu)造函數(shù)初澎,實例原型秸应,和原型之間的關(guān)系,接下來我們就說一下實例和原型之間的關(guān)系:
實例與原型
當(dāng)讀取實例的屬性時,如果找不到 就會向與實例關(guān)聯(lián)的原型的屬性上查找软啼,一直找到最頂層為止桑谍。
舉個例子:
function Person() {
};
Person.prototype.name = 'charming'
var person = new Person();
person.name = 'Lyh';
console.log(person.name) // 'Lyh'
delete person.name
console.log(person.naem) // 'charming'
這個例子中我們給person添加了一個name屬性,當(dāng)我們打印preson.name這個屬性是當(dāng)然是 'Lyh' 祸挪,但是當(dāng)我刪除了person自身的nama屬性是锣披,再去訪問這個name屬性時,
但是當(dāng)我們刪除了 person 的 name 屬性時贿条,讀取 person.name雹仿,從 person 對象中找不到 name 屬性就會從 person 的原型也就是 person.__proto__
,也就是 Person.prototype中查找闪唆,幸運的是我們找到了 name 屬性盅粪,結(jié)果為 'charming'钓葫。
如果還是沒找到呢悄蕾?原型的原型又是什么呢?
原型的原型
在前面我們也說了原型也是一個對象础浮,既然是對象帆调,我們就用最原始的方法創(chuàng)建它。例如:
var obj = new Object();
obj.name = 'charming';
console.log(obj.name) // charming
其實原型對象就是通過Object構(gòu)造函數(shù)生成的豆同,結(jié)合之前所說的番刊,實例的__proto__
指向構(gòu)造函數(shù)的prototype,所以我們再更新一下關(guān)系圖:
原型鏈
那Object.prototype的原型呢影锈?
其實就是null了芹务,我們可以打印一下:
console.log(Object.prototype.__proto__ === null); // true
那 null 究竟代表了什么呢?
引用阮一峰老師的《undefined與null的區(qū)別》就是:
null表示“沒有對象”鸭廷,即該處不應(yīng)該有值枣抱。
所以O(shè)bject.prototype.__proto__
的值為null跟Object.prototype沒有原型,其實表達(dá)了一個意思辆床。
所以查找屬性的時候查到Object.prototype就可以停止了佳晶。
最后我們再更新一下關(guān)系圖:
圖中由相互關(guān)聯(lián)的原型組成的鏈狀態(tài)結(jié)構(gòu)就是原型鏈,也就是藍(lán)色的這條線
補充一下
constructor
首先是constructor屬性讼载,我們看個例子:
function Person() {
};
var person = new Person();
console.log(person.constructor === Person); // true
當(dāng)獲取 person.constructor 時轿秧,其實person中并沒有 constructor 屬性,當(dāng)不能讀取到constructor屬性時咨堤,會從person的原型也就是Person.prototype中讀取菇篡,正好原型中有該屬性,所以:
person.constructor === Person.prototype.constructor
__proto__
其次是 __proto__
一喘,絕大部分瀏覽器都支持這個非標(biāo)準(zhǔn)的方法訪問原型逸贾,然而它并不存在于 Person.prototype 中,實際上,它是來自于 Object.prototype 铝侵,與其說是一個屬性灼伤,不如說是一個 getter/setter,當(dāng)使用 obj.__proto__
時咪鲜,可以理解成返回了 Object.getPrototypeOf(obj)狐赡。
這篇文章呢是我在另一片文章上收獲的覺得很好理解。就分享給你們了疟丙。
不喜勿噴颖侄!