js原型鏈解析

image.png


大家可以先仔細分析下該圖,然後讓我們進入主題

prototype

首先來介紹下 prototype 屬性。這是一個顯式原型屬性,只有函數才擁有該屬性。基本上所有函數都有這個屬性,但是也有一個例外

let fun = Function.prototype.bind()

如果你以上述方法創建一個函數,那麼可以發現這個函數是不具有 prototype 屬性的。

prototype 如何產生的

當我們聲明一個函數時,這個屬性就被自動創建了。

function Foo() {}

並且這個屬性的值是一個對象(也就是原型),只有一個屬性 constructor

image.png

constructor 對應着構造函數,也就是 Foo

constructor

constructor 是一個公有且不可枚舉的屬性。一旦我們改變了函數的 prototype ,那麼新對象就沒有這個屬性了(當然可以通過原型鏈取到 constructor)。

image.png

那麼你肯定也有一個疑問,這個屬性到底有什麼用呢?其實這個屬性可以說是一個歷史遺留問題,在大部分情況下是沒用的,在我的理解裏,我認爲他有兩個作用:

  • 讓實例對象知道是什麼函數構造了它

  • 如果想給某些類庫中的構造函數增加一些自定義的方法,就可以通過 xx.constructor.method 來擴展

_proto_

這是每個對象都有的隱式原型屬性,指向了創建該對象的構造函數的原型。其實這個屬性指向了 [[prototype]],但是 [[prototype]] 是內部屬性,我們並不能訪問到,所以使用 _proto_ 來訪問。

因爲在 JS 中是沒有類的概念的,爲了實現類似繼承的方式,通過 _proto_ 將對象和原型聯繫起來組成原型鏈,得以讓對象可以訪問到不屬於自己的屬性。

實例對象的 _proto_ 如何產生的

從上圖可知,當我們使用 new 操作符時,生成的實例對象擁有了 _proto_屬性。

function Foo() {}// 這個函數是 Function 的實例對象// function 就是一個語法糖// 內部調用了 new Function(...)

所以可以說,在 new 的過程中,新對象被添加了 _proto_ 並且鏈接到構造函數的原型上。

new 的過程

  1. 新生成了一個對象

  2. 鏈接到原型

  3. 綁定 this

  4. 返回新對象

在調用 new 的過程中會發生以上四件事情,我們也可以試着來自己實現一個 new

function create() {    // 創建一個空的對象
    let obj = new Object()    // 獲得構造函數
    let Con = [].shift.call(arguments)    // 鏈接到原型
	obj.__proto__ = Con.prototype
    // 綁定 this,執行構造函數
    let result = Con.apply(obj, arguments)    // 確保 new 出來的是個對象
    return typeof result === 'object' ? result : obj
}

對於實例對象來說,都是通過 new 產生的,無論是 function Foo() 還是 let a = { b : 1 } 。

對於創建一個對象來說,更推薦使用字面量的方式創建對象。因爲你使用 new Object() 的方式創建對象需要通過作用域鏈一層層找到 Object,但是你使用字面量的方式就沒這個問題。

function Foo() {}// function 就是個語法糖// 內部等同於 new Function()let a = { b: 1 }// 這個字面量內部也是使用了 new Object()

Function.proto === Function.prototype

對於對象來說,xx.__proto__.contrcutor 是該對象的構造函數,但是在圖中我們可以發現 Function.__proto__ === Function.prototype,難道這代表着 Function 自己產生了自己?

答案肯定是否認的,要說明這個問題我們先從 Object 說起。

從圖中我們可以發現,所有對象都可以通過原型鏈最終找到 Object.prototype ,雖然 Object.prototype也是一個對象,但是這個對象卻不是 Object 創造的,而是引擎自己創建了 Object.prototype 。所以可以這樣說,所有實例都是對象,但是對象不一定都是實例。

接下來我們來看 Function.prototype 這個特殊的對象,如果你在瀏覽器將這個對象打印出來,會發現這個對象其實是一個函數。

image.png

我們知道函數都是通過 new Function() 生成的,難道 Function.prototype 也是通過 new Function()產生的嗎?答案也是否定的,這個函數也是引擎自己創建的。首先引擎創建了 Object.prototype ,然後創建了 Function.prototype ,並且通過 __proto__ 將兩者聯繫了起來。這裏也很好的解釋了上面的一個問題,爲什麼 let fun = Function.prototype.bind() 沒有 prototype 屬性。因爲 Function.prototype是引擎創建出來的對象,引擎認爲不需要給這個對象添加 prototype 屬性。

所以我們又可以得出一個結論,不是所有函數都是 new Function() 產生的。

有了 Function.prototype 以後纔有了 function Function() ,然後其他的構造函數都是 function Function() 生成的。

現在可以來解釋 Function.__proto__ === Function.prototype 這個問題了。因爲先有的 Function.prototype 以後纔有的 function Function() ,所以也就不存在雞生蛋蛋生雞的悖論問題了。對於爲什麼 Function.__proto__ 會等於 Function.prototype ,個人的理解是:其他所有的構造函數都可以通過原型鏈找到 Function.prototype ,並且 function Function() 本質也是一個函數,爲了不產生混亂就將 function Function() 的 __proto__ 聯繫到了 Function.prototype 上。

總結

  • Object 是所有對象的爸爸,所有對象都可以通過 __proto__ 找到它

  • Function 是所有函數的爸爸,所有函數都可以通過 __proto__ 找到它

  • Function.prototype 和 Object.prototype 是兩個特殊的對象,他們由引擎來創建

  • 除了以上兩個特殊對象,其他對象都是通過構造器 new 出來的

  • 函數的 prototype 是一個對象,也就是原型

  • 對象的 __proto__ 指向原型, __proto__ 將對象和原型連接起來組成了原型鏈


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章