深度解析原型中的各個難點

本文同步分佈在個人 Githubgit

本文不會過多介紹基礎知識,而是把重點放在原型的各個難點上。github

你們能夠先仔細分析下該圖,而後讓咱們進入主題瀏覽器

prototype

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

let fun = Function.prototype.bind()
複製代碼

若是你以上述方法建立一個函數,那麼能夠發現這個函數是不具備 prototype 屬性的。app

prototype 如何產生的

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

function Foo() {}
複製代碼

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

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

constructor

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

那麼你確定也有一個疑問,這個屬性到底有什麼用呢?其實這個屬性能夠說是一個歷史遺留問題,在大部分狀況下是沒用的,在個人理解裏,我認爲他有兩個做用:prototype

  • 讓實例對象知道是什麼函數構造了它
  • 若是想給某些類庫中的構造函數增長一些自定義的方法,就能夠經過 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 這個特殊的對象,若是你在瀏覽器將這個對象打印出來,會發現這個對象實際上是一個函數。

咱們知道函數都是經過 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.prototypeObject.prototype 是兩個特殊的對象,他們由引擎來建立
  • 除了以上兩個特殊對象,其餘對象都是經過構造器 new 出來的
  • 函數的 prototype 是一個對象,也就是原型
  • 對象的 __proto__ 指向原型, __proto__ 將對象和原型鏈接起來組成了原型鏈

相關文章
相關標籤/搜索