理性分析 JavaScript 中的原型

原型

在類繼承的語言中,好比 Java ,使用了類來描述實例對象的行爲。JavaScript 中沒有類,因此也沒有使用類繼承。採用的是原型繼承的方式。javascript

原型繼承使用對象來描述實例對象的行爲,這個描述行爲的對象就是原型對象(prototype)。java

prototype

prototype 是全部函數都具備的屬性。當一個函數被做爲構造函數生成一個實例對象時,prototype 就是這個實例對象的原型對象。瀏覽器

constructor

constructor 表示生成該實例對象的構造函數。 可是實例對象是不存在 constructor 屬性的,這個屬性被保存在了原型對象中。緩存

來看實例:bash

function Foo(){}
var foo1 = new Foo()
console.log(foo1) 
console.log(Foo.prototype) 
console.log(foo1.constructor === Foo.prototype.constructor) 
複製代碼

結果以下: 函數

咱們能夠看到 foo1 對象中是不包含 constructor 屬性的,而 Foo.prototype 中存在 constructor 屬性。可是 foo1 的 constructor 值和 Foo.prototype 的 constructor 值倒是相等的。說明 foo1 的 constructor 屬性實際上是從 Foo.prototype 繼承過來的。這種將屬性不保存在自身,卻能經過自身訪問獲得的設計被稱爲行爲委託。post

__proto__

在上圖中,咱們看到在 foo1 對象和 Foo.prototype 對象中都有一個屬性 __proto__ 。ui

那麼 __proto__ 是什麼呢? 在繼承中,咱們須要一種向上查找的能力去維持繼承關係。對於 JavaScript 來講就是實例對象查找實例原型,子原型對象查找父原型對象,父原型對象繼續向上查找直到根原型對象,也就是 Object.prototype,Object.prototype向上查找會獲得一個 null 值,指示查找結束。整個查詢路徑構成了原型鏈。this

在瀏覽器的實現中,使用了 __proto__ 屬性來緩存原型對象,全部的對象都擁有這個屬性。這樣對象經過查詢 __proto__ 屬性便能實現向上查找。spa

來看實例:

function Foo(){}
var foo1 = new Foo()
console.log(foo1.__proto__ === Foo.prototype) // true
console.log(Foo.prototype.__proto__ === Object.prototype) // true
複製代碼

實例對象 foo1 的 __proto__ 屬性緩存着原型對象 Foo.prototype 。子原型對象 Foo.prototype 的 __proto__ 屬性緩存着父原型對象 Object.prototype。

圖示

從 constructor、prototype、__proto__ 這三個角度去思考,咱們便能很快的畫出整個圖示。

先來思考 constructor ,原型對象的構造函數表示生成實例對象的構造函數,由此得出下圖:

再來思考 prototype,構造函數的 prototype 就是原型對象。補充得出下圖:

最後來思考 __proto__,實例對象的 __proto__屬性緩存着原型對象,原型對象的 __proto__緩存着父原型對象。 實例對象是由構造函數使用 new 操做符生成的。補充得出下圖:


原型相關

instanceof

instanceof 運算符用來檢測一個對象的原型鏈中是否存在指定構造函數的原型對象。

用法以下:

object instanceof constructor
複製代碼

來看實例:

function Foo(){}
var foo1 = new Foo()
console.log(foo1 instanceof Foo) // true
console.log(foo1 instanceof Object) // true 
複製代碼

經過圖示,咱們能夠清楚地看到 Foo.prototype 和 Object.prototype 都位於原型鏈中。

Object.getPrototypeOf

ECMAScript 5 提供的 Object.getPrototypeOf 能夠用來查看實例對象的原型。

function Foo(){}
var foo1 = new Foo()
console.log(Object.getPrototypeOf(foo1 ) === Foo.prototype) // true
console.log(Object.getPrototypeOf(foo1 ) === Object.prototype) //false
複製代碼

內建對象和內建函數

關於內建對象:

  • 全部的內建對象都是由 Object() 建立而來。

關於內建函數:

  • 全部的內建函數都是由 Function() 建立而來。

理解上面的兩個要點,就能理解下面的例子了

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

Function 是內建函數是由 Function() 建立而來,因此它的 __proto__ 屬性中緩存着 Function.prototype。同理可得Object.__proto__ === Function.prototype

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

Function.__proto__是一個內建原型對象,是由 Object() 建立而來的。因此它的 __proto__屬性中緩存着 Object.prototype。同理可得 Object.__proto__.__proto__ === Object.prototype。 既然 Object.__proto__.__proto__ === Object.prototype,而 Object.prototype.__proto__ === null ,因此Object.__proto__.__proto__.__proto__ === null


總結

  • prototype 是全部函數都具備的屬性。
  • __proto__ 是全部對象(包括函數)都具備的屬性。
  • JavaScript 採用行爲委託的方式,來繼承 prototype 對象中的屬性。實例對象自己並不包含這些屬性,是經過原型鏈查找來得到這些屬性的值。
  • 在瀏覽器實現中,使用了 __proto__ 屬性來緩存 prototype 對象,整個 __proto__ 查詢路徑構成了原型鏈。
  • constructor 表示生成該實例對象的構造函數。實例對象沒有 constructor 屬性,這個屬性被保存在了原型對象中。

相關知識點

  • 繼承
  • class、extends、super、static
  • this
相關文章
相關標籤/搜索