理性分析 JavaScript 中的原型

haloislet發表於2018-03-08

原型

在類繼承的語言中,比如 Java ,使用了類來描述例項物件的行為。JavaScript 中沒有類,所以也沒有使用類繼承。採用的是原型繼承的方式。

原型繼承使用物件來描述例項物件的行為,這個描述行為的物件就是原型物件(prototype)。

prototype

prototype 是所有函式都具有的屬性。當一個函式被作為建構函式生成一個例項物件時,prototype 就是這個例項物件的原型物件。

constructor

constructor 表示生成該例項物件的建構函式。 但是例項物件是不存在 constructor 屬性的,這個屬性被儲存在了原型物件中。

來看例項:

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

結果如下:

理性分析 JavaScript 中的原型

我們可以看到 foo1 物件中是不包含 constructor 屬性的,而 Foo.prototype 中存在 constructor 屬性。但是 foo1 的 constructor 值和 Foo.prototype 的 constructor 值卻是相等的。說明 foo1 的 constructor 屬性其實是從 Foo.prototype 繼承過來的。這種將屬性不儲存在自身,卻能通過自身訪問得到的設計被稱為行為委託。

__proto__

在上圖中,我們看到在 foo1 物件和 Foo.prototype 物件中都有一個屬性 __proto__ 。

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

在瀏覽器的實現中,使用了 __proto__ 屬性來快取原型物件,所有的物件都擁有這個屬性。這樣物件通過查詢 __proto__ 屬性便能實現向上查詢。

來看例項:

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 ,原型物件的建構函式表示生成例項物件的建構函式,由此得出下圖:

理性分析 JavaScript 中的原型

再來思考 prototype,建構函式的 prototype 就是原型物件。補充得出下圖:

理性分析 JavaScript 中的原型

最後來思考 __proto__,例項物件的 __proto__屬性快取著原型物件,原型物件的 __proto__快取著父原型物件。 例項物件是由建構函式使用 new 操作符生成的。補充得出下圖:

理性分析 JavaScript 中的原型


原型相關

instanceof

instanceof 運算子用來檢測一個物件的原型鏈中是否存在指定建構函式的原型物件。

用法如下:

object instanceof constructor
複製程式碼

來看例項:

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

理性分析 JavaScript 中的原型

通過圖示,我們可以清楚地看到 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

相關文章