原型&原型鏈深度解讀

solution發表於2021-09-09

基本概念

原型鏈

首先我們看上面的示意圖,我們可以看到每一個建構函式都會有一個prototype屬性(js引擎自動幫我們加上的),這個屬性會指向一個原型物件,這個建構函式通過new 會生成一個新的例項物件,這個例項擁有一個__prto__屬性,而這個屬性也會指向原型物件。

function A () {} // 建構函式
const a = new A(); // 例項

A.prototye === a.__proro__  // true
複製程式碼

也就是說通常情況下(沒有手動給A.prototye重新賦值)建構函式的prototype和例項的__proto__指向同一地址。而原型物件會有constructor屬性指向這個建構函式。同樣原型物件也是一個物件,這個物件也會有他的__proto__屬性,這個屬性又會指向另一個原型物件,這樣一層層連結下去就構成了我們通常所說的原型鏈。

我們通常會通過instanceof這個操作符來判斷某個物件是不是某個建構函式的例項(一般我們認為一個物件的__proto__屬性和某個建構函式的prototype屬性指向同一地址instanceof就會返回true),a instanceof A返回true,這樣看來似乎並沒與什麼不妥,但是我們發現a isntanceof Object返回的也是true,a 是由Object建構函式的直接例項物件嗎?a的__proto__和Object的prototype指向同一地址嗎?顯然不是,那為什麼會返回true。其實instanceof表明的是在a這個物件的原型鏈上存在一個物件的__proto__屬性和某個建構函式的prototype屬性指向的是同一地址(翻譯過來就是:a的整條[[prototype]]鏈中是否出現過Object.prototype)。a.__proto__.__proto__ === Object.prototype這裡會返回true。其實要知道這樣的關係,我們還可以使用isPrototypeOf Object.prototype.isPrototypeOf(a),當然也可以是b.isPrototypeOf(a)

在ES5中獲取物件的原型鏈標準方法是Object.getPrototypeOf,非標準方式是a.__proto__(大多數現代瀏覽器都會支援)

但是如果這樣我們就不能判斷一個物件是不是某個建構函式的直接例項了,這時我們就可以使用constructor這個屬性

a.constructor === A //true
a.constructor === Object // false
複製程式碼

下面再來看看這張圖

原型&原型鏈深度解讀

我們從左上角說起,f2和f1是Foo建構函式的兩個例項,他們的__proto__屬性指向Foo.prototype所指向的地址(換句話說在這裡f2.__proto__Foo.prototype同一個東西)。而Foo.prototype也是一個物件,也擁有__proto__屬性,這個屬性和Object.prototype指向同一個地址,而Object.prototype.__porto__指向null(也就是說並不是每個物件都有__proto__這個屬性)因為這已經是原型鏈的頂端了。我們再看建構函式Foo其實也是一個物件(函式也是一個物件)它也擁有__proto__,它的__proto__屬性指向Function.prototype所指向的地址(即Foo.__proto__ === Function.prototype),這是因為函式物件都是有Function這個建構函式構造的。 而Function.prototype(或者Foo.__proto__.__proto__)指向Object.prototype。這裡還有中間的Object這個特殊的建構函式,他是一個函式那麼他擁有prototype屬性,同時他又是一個函式物件,那麼他就是由Function構造出來,所以Object.__proto__ === Function.prototypeFunction建構函式䦹如此。解釋起來有點麻煩,大家多看這個圖就好。所以就會出現下面這些題目了

Function instanceof Object // true 
Object instanceof Function // true 
Function instanceof Function //true
Object instanceof Object // true
Number instanceof Number //false
複製程式碼

上面說一個物件的__proto__屬性指向對應建構函式的prototype屬性所指向的地址,但是這裡如果我們新建的物件是通過Object.create函式建立,那麼新建立的這個物件的__proto__會指向crate的引數

const p = {name: 'djlxs'};
const o = Object(p);
複製程式碼

o.__proto__ === p

屬性遮蔽

當我們讀取某個物件的某個屬性時,實際上是通過[[Get]]這個操作,在物件本身沒有找到時,就會在其原型鏈上尋找直到找到或者返回undefined,當一個屬性既出現在物件本身上,又出現在原型鏈上,那麼就會優先返回物件本身相應的屬性值,因此這裡就發生了屬性遮蔽

當我們向一個物件,新增某個屬性時,如果這個屬性存在於原型鏈上,且沒有設定成只讀,那麼會在這個物件本身新建這個屬性,從而遮蔽原型鏈上的相應屬性,但是如果原型鏈上的這個屬性設定成了只讀,那麼在嚴格模式下,會丟擲相應錯誤,非嚴格模式下,則會忽略。如果在這種情況下,想要設定這個屬性,那麼我們就不能直接使用=這個賦值操作符,而是要使用Object.defineProperty()

在我們使用的要注意屬性遮蔽,這裡還有一種隱式的屬性遮蔽尤其要注意

var anotherObject = {
	a: 2
}

var myObject = Object.create(anotherObject);

myObject++;

console.log(anotherObject)  // 2
console.log(myObject) // 3
複製程式碼

因為這裡myObject++相當於myObject = myObject + 1;

注 以上參考自 《你不知道的JavaScript上卷》(144-146)

最後(歡迎大家關注我)

DJL簫氏個人部落格

部落格GitHub地址

簡書

掘金

相關文章