JavaScript ES5 的类继承
Zakas久负盛名的《JavaScript高级程序设计(第三版)》完成于2012年,当时ES5刚刚成为标准,ES6还没有浮出水面,class
还只是是保留字,由于ES5对类的支持不够完善,特别是类的构造和继承,Zakas用了整整一章来解释,第六章第三部分在讨论类继承时,提到了6种继承方式:
- 原型链
- 借用构造函数
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
作者最为推崇的是第三种组合继承方式,但是在后续的章节中,也提到了这种方式的缺点,那就是父类的构造函数实际上被调用了两次,在后续的寄生组合继承模式中有一些改进,用如下函数消除多余的一次原型副本创建(见原书P172):
function inheritPrototype(subType, superType){
var proto = object(superType.prototype);
proto.constructor = subType;
subType.prototype = proto;
}
这个函数创建一个父类原型副本,将父类的方法传递给了子类,但在这个模式中,虽然省略了创建父类型的副本,但还是创建了父类原型的一个副本,能不能连父类型原型副本的构造步骤也省略呢?
在其他面向对象语言中,子类重用父类的方法一般是通过函数指针的方法实现的,JavaScript的函数没有签名,子类不能直接获得函数方法,但是可以显示地将父类方法赋予给子类,在这个思路下,我尝试修改inheritPrototype函数,将父类原型的函数直接赋予给子类原型,既达到了重用的目的,又避免了多余的副本创建,代码示例如下:
function inheritPrototype(sub, base) {
for (var propertyName in base.prototype) {
if (typeof base.prototype[propertyName] === 'function') {
sub.prototype[propertyName] = base.prototype[propertyName];
}
}
}
这个函数遍历父类原型中所有的方法,然后将方法直接赋予给子类原型,这样子类就可以完全重用父类的功能代码,也避免了创建对象。
改造之后,不仅达到了预期,还可以使用构造函数来创建类的实例,类的行为和其他的OO语言也没有区别,完整代码如下所示,在nodejs环境中运行一切Ok。
'use strict';
//===================
main();
//===================
function main() {
var tmp1 = new Person('Dani', 28);
tmp1.sayName();
tmp1.sayAge();
var tmp2 = new Worker('Kayden', 34, 'Actress');
tmp2.sayName();
tmp2.sayAge();
tmp2.sayJob();
var tmp3 = new Worker('Luna', 30, 'Writer');
tmp3.friends.push('Sunny');
tmp3.sayName();
tmp3.sayAge();
tmp3.sayJob();
return 0;
}
function printf(value) {
console.log(value);
}
// sub类继承base类原型的所有方法
function inheritPrototype(sub, base) {
for (var propertyName in base.prototype) {
if (typeof base.prototype[propertyName] === 'function') {
printf('Subtype inherited a function: ' + propertyName);
sub.prototype[propertyName] = base.prototype[propertyName];
}
}
}
//父类
function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ['Jenna', 'Carrera', 'Tori'];
if (typeof this.sayName !== 'function') {
Person.prototype.sayName = function() {
printf(this.name);
};
Person.prototype.sayAge = function() {
printf(this.age);
};
}
}
// 子类
function Worker(name, age, job) {
Person.call(this, name, age);
this.job = job;
if (typeof this.sayName !== 'function') {
// 组合继承方式,为了重用父类方法,构造了一个多余的父类
// Worker.prototype = new Person();
// 寄生组合方式,改造就在此函数之内
inheritPrototype(Worker, Person);
Worker.prototype.sayJob = function() {
printf(this.job);
};
}
}
写在后面的话
ES6已经完全支持class
,构造和继承变得非常简单,和其他OO语言已经大同小异了,不过考虑到目前还有很多浏览器对ES6的支持不够完善,ES5的类继承还会陪伴我们走过很长一段时间。