1. 程式人生 > 其它 >JavaScript中簡明扼要的原型鏈

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)得來,那麼infoInfo之間的關係通過什麼維護
  • 這種原型鏈的設計方式意在何為,有沒有別的實現方式

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本身不提供其他方法,但是因為其組合了obj1obj2兩個物件,那麼變相的等價其同時擁有了obj1obj2的屬性和方法。

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物件中所謂「繼承」的祕密所在。