JavaScript中簡明扼要的原型鏈
網上看了一圈關於JavaScript
這門語言的所謂「原型鏈」概念,魚龍混雜,一些示意圖毫無章法略微混亂,這篇小文不講這些,只闡明3個屬性的概念。
-
JavaScript
中有一種資料型別叫「物件」(在此不贅述物件具體是什麼,不是什麼) -
JavaScript
中有一種資料型別叫「函式」,函式是一種特殊的「物件」(有多特殊?特殊在這個物件有個專有名詞叫函式,這個物件還能被呼叫執行) - 所以顯然,物件不一定是函式,函式一定是物件。
- 所有「物件」都有一個屬性,叫「
__proto__
」 - 所有「函式」都有一個屬性,叫「
prototype
」 - 因為函式也是一種物件,所以,所有函式都有一個屬性,叫「
__proto__
綜上:
function Info(){ console.log("info") } let info = new Info() /* 這裡有2個物件,更準確的說是1個物件1個函式。因為函式也是物件,所以這裡有2個物件。 其中, info.__proto__指向某個物件{}(不妨稱這個物件為info.__proto__物件,額~似乎有點蠢...) Info.prototype也指向某個物件{}(不妨稱....) 並沒有什麼意外,info.__proto__與Info.prototype指向的是同一個物件: 即:info.__proto__ === Info.prototype -> {一片記憶體} */
根據上面程式碼演示,可知info.__proto__
指向Info.prototype
物件
可見,無論是info
中的欄位__proto__
還是Info
中的欄位prototype
,都是指向同一塊記憶體區域中的所謂「原型」物件,既然這個「原型」也是一個物件,那必然也擁有一個叫__proto__
的欄位:
顯然,這個記憶體中的「原型」物件的欄位__proto__
指向一個叫Object
物件的prototype
欄位並最終一起指向另一個記憶體中的「原型」物件。
既然Object
這個物件擁有欄位prototype
,顯然,它是個函式。
事情逐漸奇怪起來,既然圖示中「綠色」所示的也是一個原型「物件」,那麼這個物件的__proto__
__proto__
被設定為了null
(呵呵邏輯自洽哪有什麼祕密可言,總在哪出動了點小手腳而已)。
所以至此,原型鏈在null
處終結,一切真相都在眼前。
這裡還有2個小問題需要解釋下:
-
info
物件是由Info
函式構造(new
)得來,那麼info
與Info
之間的關係通過什麼維護 - 這種原型鏈的設計方式意在何為,有沒有別的實現方式
JavaScript
通過constructor
欄位來關聯物件與「物件構造器」之間的關係。這種形式在普通的面嚮物件語言中就是「物件」與「類」的關係,但是JavaScript
從本質上並不提供「面向物件」的實現,只是從介面協議上存在對「面向物件」的形式支援,做到了形似和神似。(所謂面向物件的「支援」不僅僅是指在語法形式上,記憶體結構上也需要支援,而JavaScript
物件的記憶體結構並非傳統面向物件程式語言的記憶體佈局)
在prototype
所指物件中的constructor
欄位所表示的就是該物件的構造器函式名字,意為通過該構造器函式生成的物件。
function Item(){
}
const item01 = new Item()
console.log(item01.constructor.name)//列印Item
上面簡單的幾行程式碼有幾個說明:
1.item01
物件「本身」並沒有一個叫constructor
的欄位,這個欄位來源於item01.__proto__
所指向的Item.prototype
物件(此處,原型鏈的作用可見一斑了,且按下不表)
2.既然item01
這個物件由Item
構造而來,那Item
這個物件由什麼構造而來呢?答案是「Function
」,顯然,為了邏輯自洽這個Function
物件(函式)是內建的。
function Item(){
}
console.log(Item.constructor)//[Function: Function]
//同理,Item物件自身並沒有constructor這個欄位,該欄位位於Item.prototype上
JavaScript
中的原型鏈本質是一種「組合」的設計模式。而非通過物件繼承來實現程式碼複用。
let obj1 = {}
let obj2 = {}
let obj3 = {
obj1:obj1,
obj2:obj2
}
以上,就是典型的組合模式。
物件obj3
本身不提供其他方法,但是因為其組合了obj1
和obj2
兩個物件,那麼變相的等價其同時擁有了obj1
和obj2
的屬性和方法。
JavaScript
中建立一個物件,其本身不一定存在toString()
方法,但是這個物件有一個__proto__
物件,這個物件指向的原型物件提供了toString()
方法,這樣就變相等價這個物件擁有了toString()
方法。
在JavaScript
中,當說到一個物件繼承了某個物件,從而擁有了某個屬性和方法,本質是因為這個物件組合了某個物件,從而擁有了那個物件的屬性和方法,而這個所謂的組合,就是通過其__proto__
而來的。
let obj = {}
obj.__proto__sayitem = function(){
console.log('hello item')
}
obj.sayitem()//輸出hello item
顯然,obj
物件本身並不存在sayitem
方法,通過__proto__
欄位找到原型物件後,在原型物件身上找到了sayitem
方法,這樣「看起來」obj
物件就擁有了sayitem
方法。而__proto__
就是JavaScript
物件中所謂「繼承」的祕密所在。