javascript有八種類型的數據,其中沒有function,是因為function被object這個大類包含了。也就是説,所有函數都是object。
當我們自己聲明瞭一個類,我們拿到的引用是一個函數的引用,它默認繼承於內置的Object對象,但是由於javascript最頂層的父級(Object和Function)設計有點亂,並且不具有普適性,在這裏我們用兩個普通的類來演示繼承的實現。
將子類聲明的引用原型的[[Prototype]]指向父類聲明的引用原型,這是繼承實現的一部分。另一部分是子類聲明的引用的[[Prototype]]指向父類聲明的引用,完整代碼如下:
class A {
}
class B {
}
// B 的實例繼承 A 的實例
// 第一部分: 將B.prototype的[[Prototype]]指向A.prototype
Object.setPrototypeOf(B.prototype, A.prototype);
// B 繼承 A 的靜態屬性
// 第二部分: 將B的[[Prototype]]指向A
Object.setPrototypeOf(B, A);
const b = new B();
以下討論忽視頂層父類Object和Function,僅討論普通的存在繼承的類之間的關係。
剛才説了,繼承只需要實現兩部分,如下圖:
如果要知道為什麼現在這個方法要執行兩步,添加兩個[[Prototype]]引用,那就要知道構造函數是怎麼提供我們所看見的”類“的。
首先,在構造函數聲明的時候,引擎就創建了兩個對象:一個是構造函數本身的function對象,一個是構造函數的原型對象——一個類型為object的對象,它被構造函數的prototype屬性所引用。構造函數上的屬性可以通俗地被理解為靜態屬性,可以不生成實例就直接調用(這麼想,構造函數本身也是一個function對象,它在聲明的時候就存在了,它的屬性就可以使用,和我們new出來才能使用屬性的實例本質上是一樣的);prototype對象的屬性只能被實例調用,且被所有實例共享。
然後,在調用new的時候,新創建的對象在執行構造函數之前,就已經將它的[[Prototype]]屬性設置給了構造函數的原型對象。借用上圖,效果應該是這樣的:
所以這就體現出了我們右邊那部分:Son.prototype.[[Prototype]] = Father.prototype的重要性了。根據js原型鏈規則,在尋找一個屬性的時候,如果在當前對象上沒找到,則會順着它的原型鏈,也就是[[Prototype]]屬性,一個一個往上面的對象找。所以在我給的例子中,查找順序如下:
其中從Son.prototype到Father.prototype那條線,是我們通過實現繼承連起來的。所以做了這一步之後,Son這個類的實例中找不到的屬性就可以到Father.prototype,乃至這條鏈上更遠的原型對象上尋找屬性。
本來繼承的實現應該到這裏就結束了,類實例和其它語言一樣,已經可以訪問所有父類的屬性了,但是還不夠完善:類的靜態屬性和方法也需要繼承。在es5中,靜態屬性和方法直接寫在構造函數這個function對象的屬性中;到了es6則是在類聲明裏使用static關鍵字標識。
所以我們給(理論上具有父子關係的)構造函數對象添加[[Prototype]],將它們組織成原型鏈,這樣在尋找Son的靜態方法時,會按照如下順序找:
這就是完整的,es6的類繼承解決方案。
實現:我這裏貼兩個實現,一個是es5的寄生組合式繼承,一個是es6的extends繼承及原理。
es5誕生了很多繼承方式,主要有原型鏈繼承、借用構造函數、組合式繼承、寄生式繼承和寄生組合式繼承。其中寄生組合式繼承是最優,代碼如下:(來源:https://segmentfault.com/a/11...)
//父類
function SuperType(name){
//父類實例屬性
this.name = name;
this.colors = ["red", "blue", "green"];
}
//父類原型方法
SuperType.prototype.sayName = function(){
alert(this.name);
};
//子類
function SubType(name, age){
SuperType.call(this, name);//1.借用構造函數:繼承父類的實例屬性;
this.age = age;
}
//2.寄生式繼承:將父類原型的副本強制賦值給子類原型,實現繼承父類的原型方法。
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //創建父類原型的副本
prototype.constructor = subType; //將該副本的constructor屬性指向子類
subType.prototype = prototype; //將子類的原型屬性指向副本
}
因為es5條件有限,不支持直接操作[[Prototype]],所以只能通過創建原型對象的副本這樣彆扭的方式實現。而且最終也沒實現靜態屬性的繼承。
下面是es6的代碼:(來源:Class 的繼承 - ECMAScript 6入門 (ruanyifeng.com))
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg); // super 會在後文講
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1
var child = new Child();
child.myMethod(2); // instance 2
es6的代碼就很簡明。
最後提一嘴super關鍵字吧。
在es6中,如果子類有constructor的話,則必須調用super的構造方法,調用完之後才能使用this。
super關鍵字保留了它在聲明時的類引用,並在實際調用時指向它當前的父類。
super關鍵字我目前瞭解的還是這些,過後可能會看ECMA規範之類的比較權威有深度的文檔弄清楚。