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规范之类的比较权威有深度的文档弄清楚。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。