JS對象繼承與原型鏈

對象繼承 VS 類繼承

在 class-based 的面向對象的世界裏,要出現對象,必須先有類。類之間能夠繼承,類再使用 new 操做建立出實體,父子對象之間的繼承體如今父類和子類上。你不能說 對象 a 繼承了對象 b,只能說 class A 繼承了 class B,而後他們各自有一個實例a、b。app

JS中實現的是原型繼承。在 prototype-based 的面向對象的世界裏,繼承是對象之間的事情,就像現實世界裏,兒子繼承父親,不必非得整出父親類、兒子類。你有一個對象 a,經過 b = Object.create(a) (或者用相似的polyfill)建立出一個繼承了 a 對象的實體 b。a 對象的屬性和方法甚至後期對 a 的屬性和方法修改,b 對象都直接繼承過來。這裏,咱們說,a對象是b對象的原型。咱們發現,這樣的繼承方式不須要,繼承徹底在對象間完成。函數

原型繼承的工做機制

在對象屬性查找時,若是對象自己存在這個屬性,會優先使用它本身的(也就是概念 ownProperty);若是沒有,就會查找對象的原型上有沒有這個屬性;若是原型對象也有本身的原型,遞歸查找,直到原型的根部,沒有原型了中止查找,若是仍是不存在,則返回 undefined。這就是傳說中的 原型鏈,這也是 JS 中對象繼承的體現方式,原型存在的意思。this

這裏順帶說一下如何獲取一個對象的原型。ES5 提供了 Object.getPrototypeOf(obj) 方法來獲取一個對象的原型,在 Chrome 中也可使用非標準的 obj.__proto__prototype

JS 在 prototype-based 的面向對象的基礎上,引入了 構造器 來模擬 class-based 的模式, 配合 new 操做符使用。 構造器和 已有的 prototype 概念如何配合工做呢?翻譯

咱們知道,JS 中的構造器就是一個普通函數,可是這個函數有一個特殊的屬性(函數也是對象,因此也有屬性) ———— prototype。此 prototype 用來定義經過構造器構造出來的對象的原型,構造器內部的代碼用來給對象初始化。code

function Ctor() {}
console.dir(Ctor.prototype);
// 構造器的 prototype 屬性,默認值是
// { constructor: Ctor    }

Ctor.prototype.method = function() {
    console.log(1)
}

instance = new Ctor();

instance.constructor // Ctor
instance.method() // console.log(1)

instance 是如何得到 Ctor 的 prototype 屬性上的數據的呢?好,還記得 JS 中的繼承都是對象之間的繼承嗎?咱們翻譯一下 new 操做符到底幹了什麼。對象

instance = new Ctor() // 等價於
instance = Object.create(Ctor.prototype) // 用 Ctor 的 prototype 做爲原型來建立一個新對象
Ctor.apply(instance) // 執行構造器用來初始化,構造器中的 this 指向 instance

咱們稱, instance 的原型是 Ctor.prototype, instance 是 Ctor 構造出來的 (new 出來的).繼承

爲了讓 instance.constructor 能正確指向 instance 的構造器,一個構造器默認的 prototype 上已經存在 constructor 屬性,而且指向構造器自己了。在咱們覆蓋構造器的 prototype 屬性時,記得要把 prototype.constructor 屬性定義了,讓它指回到構造器,不然構造出來的 instance 的 constructor 屬性就出問題了。因此咱們能夠看出,instance.constructor 實際上是不是 instance 本身的屬性,是原型鏈上定義的。遞歸

這裏千萬不要把 Ctor.prototype 誤理解爲是 Ctor 的原型。Ctor 的原型是 Object.getPrototypeOf(Ctor)(非標準寫法:Ctor.__proto__),它是 Function.prototype, 由於 Ctor 是一個函數對象,全部函數都構造自 Function,原型是 Function.prototype。Ctor.prototype 是 Ctor 構造出來的實例的原型,不是 Ctor 的原型。原型鏈

Object & Function 雞生蛋蛋生雞

有代碼以下:

Object instanceof Function // true
Function instanceof Object // true
// what???

咱們來挖掘一下 instanceof 操做符底層邏輯:

instance instanceof Ctor // 等價於

function instanceOf(instance, prototype) {
    var proto = Object.getPrototype(instance); // 取對象原型
    if( proto === null) return false; // 空
    if( proto === prototype) return true; // 原型匹配
    return instanceOf(proto, prototype); // 遞歸檢查原型的原型
}

instance(instance, Ctor.prototype);

JS 中的繼承終歸是原型的繼承,因此 class-based 中的 instanceof 概念最終也須要映射到 prototype 上。可是 JS 中的構造器名稱有一個特殊之處,這個名稱既表示了構造器這個函數,又表示了 class-based 概念中的 的概念, 而函數自己又是一種特殊的對象。

Object instanceof Function 之因此爲 true,咱們是把 Object 當作構造器看待,它是一個函數,它是 Function 的實例,因此同時這裏咱們把 Function 看成類型來看待,它是全部 function 的類。

Function instanceof Object 之因此爲 true,咱們是把 Function 看成對象看待,它雖然是一個構造函數,可是它也是對象,它是 Object 的實例,因此同時咱們又把 Object 看成類型來看待,它是全部對象的類。

從原型角度:

// Object 是一個構造函數,它的原型是 Function.prototype
// Function.prototype 是全部函數的原型,call, apply 就掛在這裏
Object.getPrototypeOf(Object) === Function.prototype 

// Function 也是一個構造函數
Object.getPrototypeOf(Function) === Function.prototype
// Function.prototype 自己是一個對象,全部對象的原型根部都是 Object.prototype
Object.getPrototypeOf(Function.prototype) === Object.prototype

這也印證了 JS 中函數是對象的概念。

相關文章
相關標籤/搜索