Before we get started
首先明確,JS的繼承是由原型鏈來實現的。(即使在ES6中class的extends,也更像是一種語法糖)
當談到繼承時,JavaScript 只有一種結構:對象。每個實例對象(object )都有一個私有屬性(稱之為 _proto_)指向它的原型對象(prototype)。該原型對象也有一個自己的原型對象 ,層層向上直到一個對象的原型對象為 null。根據定義,null 沒有原型,并作為這個原型鏈中的最后一個環節。
C++和Java使用new命令時,都會調用"類"的構造函數(constructor)
在Javascript語言中,new命令后面跟的不是類,而是構造函數
在 JavaScript 中,構造器其實就是一個普通的函數。當使用 new 操作符 來作用這個函數時,它就可以被稱為構造方法(構造函數)。
那么new運算符具體干了什么呢?
var obj = {};
obj.__proto__ = F.prototype;
F.call(obj);
即
var o = new Foo();
// JavaScript 實際上執行的是:
var o = new Object();
o.__proto__ = Foo.prototype;
Foo.call(o);
關于_proto_和prototype
那么上面的_proto_和prototype又是什么呢?
對象具有屬性_proto_,可稱為隱式原型,一個對象的隱式原型指向構造該對象的構造函數的原型。
eg.
let o1 = new Object()
// 則
o1.__proto__ === Object.prototype // true
而Function是特殊的對象,在Function中,除了和其他對象一樣有_proto_對象以外,還有原型屬性(prototype),這個屬性指向原型對象,而原型對象有個叫constructor的屬性,指回原構造函數。
eg.
let f1 = new Foo()
// 則
f1.__proto__ === Foo.prototype
Foo.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
Foo.prototype.constructor === Foo
Object的原型對象用Object.prototype._proto_ = null表示原型鏈的最頂端,如此變形成了javascript的原型鏈繼承,同時也解釋了為什么所有的javascript對象都具有Object的基本方法。
Object關于prototype的兩個方法
// 等同于 B.__proto__= A
Object.setPrototypeOf(B, A)
// 從子類上獲取父類
Object.getPrototypeOf(B)
JavaScript實現繼承的幾種方式
構造繼承
function dog (name) {
this.name = name
}
// 共享的屬性放在prototype中,只要更改prototype,所有實例的master屬性都會被更改
dog.prototype = { master: 'Fog' }
// 生成兩個實例對象
let dog1 = new dog('111')
let dog2 = new dog('222')
console.log(dog1.master) // Fog
console.log(dog2.master) // Fog
// 更改master
dog.prototype.master = 'Misty'
console.log(dog1.master) // Misty
console.log(dog2.master) // Misty
由于所有的實例對象共享同一個prototype對象,那么從外界看起來,prototype對象就好像是實例對象的原型,而實例對象則好像"繼承"了prototype對象一樣。
原型繼承
即:把父類的私有+公有的屬性和方法,都作為子類公有的屬性。
核心思想:<code>Child.prototype = new Parent()</code>
實現的本質是重寫了原型對象 ,通過將子類的原型指向了父類的實例,所以子類的實例就可以通過_proto_訪問到 Child.prototype 也就是 Parent的實例
實例繼承
核心思想:
function Child() {
let instance = new Parent()
return instance
}
拷貝繼承
核心思想:循環遍歷父類實例,然后父類實例的私有方法全部拿過來添加給子類實例
Call繼承
核心思想:
function Child() {
Parent.call(this); //構造函數中的this就是當前實例
}
注意:call是function的方法
中間件繼承
核心思想:<code>Child.prototype._proto_ = Parent.prototype</code>
Object.create()
ECMAScript 5 中引入了一個新方法:Object.create()??梢哉{用這個方法來創建一個新對象。新對象的原型就是調用 create 方法時傳入的第一個參數。
ES6的extends
首先說明,ES6的Class中,所有定義的function方法(注意:在定義類的方法時,前面不需要加上 function 這個保留字),都是定義在prototype屬性上的。
即:在類的實例上調用方法,其實就是調用原型上的方法。
此外,在類的內部定義的所有方法都是不可枚舉的。(與ES5不同,注意)
Class之間可以通過extends關鍵字實現繼承。
子類必須在constructor方法中調用super方法,否則新建實例時會報錯。這是因為子類沒有自己的this對象,而是繼承了父類的this對象。如果不調用super方法,子類就得不到this對象。
這與ES5不同:ES5是先創建子類的this,再將父類的方法添加到this上(Parent.apply(this))
而ES6:先創建父類的this,然后再用子類的構造函數修改this
而super指向父類的原型對象,所以定義在父類實例上的方法或屬性,是無法通過super調用的