深刻JavaScript(一): 原型與原型鏈

__proto__.jpeg

注:本文首發於個人博客,移步 獲取更好的閱讀體驗。git

拋開封面圖,咱們先以 MDN 的一句話做爲開端,github

對於使用過基於類的語言 (如 Java 或 C++) 的開發人員來講,JavaScript 有點使人困惑,由於它是動態的,而且自己不提供一個 class 實現。雖然在 ES2015/ES6 中引入了 class 關鍵字,但那只是語法糖,JavaScript 仍然是基於原型的瀏覽器

那麼到底什麼是原型?less

原型

咱們先用構造函數建立一個實例對象,函數

其實理解原型,就是理解構造函數,實例對象和原型對象之間的關係ui

function Engineer(name) {
  this.name = name
}

Engineer.prototype.coding = function() {
  console.log('write less, do more.')
}

const engineer = new Engineer('campcc')
複製代碼

JavaScript 中,每個構造函數都有一個 prototype 屬性,它指向構造函數的原型對象:this

Engineer.prototype // {coding: ƒ, constructor: ƒ}
複製代碼

原型對象中有一個 constructor 屬性指回構造函數:spa

Engineer.prototype.constructor === Engineer // true
複製代碼

而每個實例對象都有一個 __proto__ 屬性,當咱們使用構造函數建立實例時,實例的 __proto__ 屬性就會指向構造函數的原型對象:prototype

engineer.__proto__ === Engineer.prototype // true,__proto__ 實際上是一個 JavaScript 的非標準但許多瀏覽器都實現的屬性,從 ECMAScript 6 開始,支持經過符號 [[Prototype]] 或者方法 Object.getPrototypeOf() 訪問
複製代碼

構造函數,實例對象與原型對象的關係爲:3d

構造函數實例對象和原型對象關係圖.png

原型鏈

爲了更好的理解什麼是原型鏈,咱們嘗試調用實例對象的幾個方法,

engineer.coding() // write less, do more.

engineer.toString() // "[object Object]"

engineer.map() // Uncaught TypeError: engineer.map is not a function
複製代碼

結果看似很出乎意料,由於咱們其實並無在實例裏定義 codingtoString 方法啊,可是它們卻可以被成功調用,爲何?由於在 JavaScript 中,當咱們試圖訪問一個對象的屬性或方法時,它不單單在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索:

原型鏈.png

咱們嘗試訪問 map 方法時報錯了,由於原型鏈上找不到此方法。原型鏈查找會一直持續,直到找到一個名字匹配的屬性或方法,或者達到原型鏈的末尾,而根據定義,null 就是原型鏈的末尾

Object.prototype.__proto__ === null // true,null 在這裏能夠理解爲 「沒有對象」
複製代碼

若是查找過程當中遇到同名屬性或方法,位於原型鏈底端的屬性或方法會被優先應用,這叫作 「屬性遮蔽(property shadowing)」。好比咱們在 Engineer 的原型對象上聲明一個同名的 name 屬性:

Engineer.prototype.name = 'engineer'

engineer.name // campcc,這裏不會打印 'engineer',由於在原型鏈查找的過程當中,實例對象中就已經存在 name 屬性了
複製代碼

總結一下,原型鏈其實就是對象或原型對象的 __proto__ 組成的一條原型查找鏈

幾乎全部 JavaScript 中的對象都是位於原型鏈頂端的 Object 的實例,但有兩個例外:

  1. Object.prototype:咱們剛纔說了,它的原型是 null
  2. Object.create(null):建立一個原型爲 null 的空對象

補充

關於封面圖,其實隱喻了 JavaScript 中一直存在爭議的一個問題,

Function.__proto__ === Function.prototype // true
複製代碼

先來看看在 Chorme V8 中的打印結果:

Function.__proto__ // ƒ () { [native code] }

Function.prototype // ƒ () { [native code] }

Object.__proto__ // ƒ () { [native code] }
複製代碼

從打印結果來看,根本不存在 "Function 也是 Function 自己的一個實例" 的說法,由於無論是 Function.__proto__Function.prototype 仍是 Object.__proto__ 都是引擎建立的,Function 也是由引擎建立的,至於爲何會相等,只能說每一個語言都存在缺陷,這個問題就像爲何 typeof null === "object" 同樣,不用過於糾結。

總結

JavaScript 中,

  • 每一個函數都有 prototype 屬性,表明構造函數的原型對象
  • 每一個實例對象都有 __proto__ 屬性,指向構造函數的原型對象
  • 每一個原型對象中都有 constructor 屬性,指向構造函數,標識原型對象的是由哪一個函數構造的
  • 對象或原型對象的 __proto__ 組成了一條原型鏈,原型鏈其實也是一條查找鏈
  • 原型鏈查找會一直持續,直到找到同名的屬性或方法,或者到達原型鏈的末尾 null
  • 原型鏈查找會遵循 屬性遮蔽 原則,位於底層的屬性或方法會被優先找到

勘誤與提問

若是有疑問或者發現錯誤,能夠在相應的 issues 進行提問或勘誤

若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵

(完)

相關文章
相關標籤/搜索