我們知道ES6增加了很多新特性,class是其中一個。
是不是ES支持類式繼承了呢,其實(shí)不然。
class是一個語法糖,ES仍然是一個基于原型繼承的編程語言。
我們先來熟悉一下class的用法吧:
class Base{
constructor(){
this.v2=2;
this.v3=4;
}
}
class Sub extends Base{
constructor(){
super();
this.v1=1;
this.v3=3;
}
}
var s=new Sub();
console.assert(s.v1===1);
console.assert(s.v2===2);
console.assert(s.v3===3);
現(xiàn)在我們想做的是模擬一個簡潔的實(shí)現(xiàn)。
那還要從設(shè)計(jì)用例開始。
我們打算讓用戶這樣使用,且能跑過以下測試。
var Base=Class.extend({
v2:2,
v3:4
});
var Sub=Base.extend({
v1:1,
v3:3
});
var s=new Sub();
console.assert(s.v1===1);
console.assert(s.v2===2);
console.assert(s.v3===3);
我們來分析一下實(shí)現(xiàn)思路吧。
在考慮實(shí)現(xiàn)的時候,最關(guān)鍵的是如何讓兩個原型對象串聯(lián)起來。即,
var baseProto={v2:2,v3:4};
var subProto={v1:1,v3:3};
怎樣讓subProto.__proto__===baseProto
這樣的話在subProto中尋找某屬性v時,如果找不到,就會到它的__proto__中的去找,這就是原型繼承的原理了。
我們首先能想到的實(shí)現(xiàn)方案是:
function C(){
return subProto;
}
C.prototype=baseProto;
C.prototype.constructor=C;
var instance=new C;
根據(jù)構(gòu)造函數(shù)生成實(shí)例的原理,instance.__proto__==C.prototype,
而且C.prototype===baseProto,instance===subProto
于是,我們有subProto.__proto__===baseProto
然而,這是不正確的。
因?yàn)閟ubProto是一個引用類型的對象,構(gòu)造函數(shù)如果返回引用類型的對象,那么就會丟掉this,直接返回這個引用類型的對象。
因此,返回的subProto并不是C的實(shí)例,并不滿足instance.__proto__==C.prototype條件。
那怎么辦呢?另一個方案是,
我們可以建立一個新的空對象empty,讓它的__proto__為baseProto,
然后把subProto中的所有自身屬性都復(fù)制到empty中。即,
var empty=Object.create(baseProto);
var proto=Object.assign(empty,subProto);
其中,proto就是使用subProto填充過的empty對象。
這樣在尋找proto的屬性v時,實(shí)際上是查找empty的屬性v,
這個emtpy自身的屬性都是從subProto復(fù)制過來的,于是相當(dāng)于優(yōu)先查找subProto,
如果找不到,那么就會查找empty.__proto__,即baseProto
這樣在原型鏈上把subProto和baseProto串聯(lián)起來了。
有了這個串聯(lián)技巧,剩下的就容易實(shí)現(xiàn)了。
(function(global){
'use strict';
Class.extend=extend;
global.Class=Class;
//private
function Class(){}
function extend(proto){
var Super=this,
Sub=createConstructor(Object.assign(Object.create(Super.prototype),proto));
Sub.extend=extend;
return Sub;
}
function createConstructor(proto){
var C=function(){};
C.prototype=proto;
C.prototype.constructor=C;
return C;
}
}(window));
"chrome://flags/"中啟用"啟用實(shí)驗(yàn)性 JavaScript Mac, Windows, Linux, Chrome OS, Android"之后,
測試順利通過了。
參考:
ECMA-262, Edition 6, June 17 2015
Simple JavaScript Inheritance
Classes - JavaScript | MDN