JS中没有其他面向对象中的“类”的概念,其继承方式主要通过“原型”,具体包括:单纯的原型链、借用构造函数(constructor stealing)、组合继承、原型式继承、寄生式继承、寄生组合式继承。其中寄生组合式继承被认为是引用类型最理想的继承范式。(上述几种继承范式的详细介绍,请查阅Nicholas C.Zakas所著Professional Javascript for Web Developers 3rd edition)
寄生组合式继承定义: 通过借用构造函数来继承属性,通过原型链来继承方法。此之所谓“组合”;由于指定“子类”的原型对象(prototype)时,所需的不过是“父类”原型的一个副本(浅拷贝即可),并不必调用“父类”的构造函数。本质上就是使用寄生式继承来继承“父类”的原型,再将结果指定给“子类”的原型。(这里为了便于描述使用了“父类”、“子类”,实际上称为superType、subType更准确)。
寄生组合式继承基本模式:
function inheritPrototype(SubType, SuperType) {
let prototype=Object.create(SuperType.prototype); // 创建对象
prototype.constructor=SubType; // 增强对象
SubType.prototype=prototype; // 指定对象
}
代码块1
而在组合式继承中,则是通过代码块2所示,调用“父类”构造函数创建了一个实例,将此实例赋予“子类”的原型对象,这使得“子类”的原型对象中混入了“父类”实例属性,这往往并不是我们想要的。结果就需要在“子类”的构造函数SubType中,再次调用“父类”构造函数SuperType,来重写“子类”的原型对象中混入的“父类”实例属性。这种做法虽然可行,不过并不完美。
function inheritPrototype2(SubType, SuperType) {
SubType.prototype=new SuperType(); //第一次调用SuperType
SubType.prototype.constructor=SubType;
}
代码块2
function SubType(可选初始化参数) {
SuperType.call(this, 其他参数) ; //第二次调用SuperType
}
代码块3
寄生组合式继承优点: (1)只调用了一次SuperType构造函数,避免了在SubType.prototype上创建多余的属性;
(2)原型链保持不变,仍可正常使用instancesof 和 isPrototypeOf()来检测实例与特定引用数据类型、“父类”与“子类”继承关系。
下面两段代码分别说明继承单一类型和多个类型。
// Shape - 父类(superclass)
function Shape() {
this.x = 0;
this.y = 0;
}
// 父类的方法
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - 子类(subclass)
function Rectangle() {
Shape.call(this); // call super constructor.
}
// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
var rect = new Rectangle();
console.log('Is rect an instance of Rectangle?',
rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?',
rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
代码块4 继承单一类型
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
// do a thing
};
代码块5 继承多个类型
Note:
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的_proto_。
Object.assign会把OtherSuperClass
原型上的函数拷贝到MyClass
原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。Object.assign 是在 ES2015 引入的,且可用 polyfilled。
ES6为了在继承上与传统面向对象语言更加类似,引入了class
。
ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到 this 上面(所以在 constructor 里必须先调用super方法),然后再用子类的构造函数修改this。