Javascript的继承和传统的OO语言不一样,有一些特别的地方需要我们去注意。文章作为阅读红皮书(JS高程)的笔记,记录Javascript语言中的继承方式。
function SuperType() { this.property = true;
} SuperType.prototype.getSuperValue = function() { return this.property;
} function SubType() { this.subproperty = false;
} // 继承 SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subproperty;
} const instance = new SubType(); instance.getSuperValue() // 输出SuperType中的true
原型链继承有它的缺点,就是包含引用类型值的原型属性会被所有实例共享,如下面这个例子
function SuperType() { this.colors = ['red', 'blue', 'green'];
} function SubType() { } SubType.prototype = new SuperType(); const instance1 = new SubType(); instance1.colors.push('black'); alert(instance1.colors); // "red, blue, green, black"; const instance2 = new SubType(); alert(instance2.colors); // "red, blue, green, black";
这个例子中的colors就是一个会被所有实例共享的对象。
原型继承的另一个缺点是在创建子类的实例时,不能向超类的构造函数传递参数。
function SuperType() { this.colors = ['red', 'blue', 'green'];
} function SubType() { // 继承 SuperType.call(this);
} const instance1 = new SubType(); instance1.colors.push('black'); alert(instance1.colors); // "red, blue, green, black"; const instance2 = new SubType(); alert(instance2.colors); // "red, blue, green";
通过SuperType.call(this)在SubType的对象上执行SuperType函数中定义的所有对象初始化代码。
并且借用构造函数还可以传递参数
function SuperType(name) { this.name = name;
} function SubType() { // 继承 SuperType.call(this, 'Michael'); this.age = 25;
} const instance = new SubType(); alert(instance.name); // 'Michael' alert(instance.age); // 25
但借用改造函数继承也有它的缺点,即无法避免构造函数模式存在的函数复用的问题,因为所有的方法都在构造函数中定义。
组合继承将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
思路是使用原型链实现对原型属性和方法的继承,而通过构造函数来实现堆实例属性的基继承。
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); this.age = age;
} // 继承方法 SubType.prototype = new SuperType(); SubType.prototype.sayAge = function() { alert(this.age);
} const instance1 = new SubType('Michael', 25); instance1.colors.push('black'); alert(instance1.colors); // "red, blue, green, black" instance1.sayName(); // "Michael" instance1.sayAge(); // 25 const instance2 = new SubType('John', 27); alert(instance2.colors); // "red, blue, green" instance2.sayName(); // "John" instance2.sayAge(); // 27
组合继承解决了原型链与借用构造函数的缺陷,虽然它也有一定的问题,但它还是Javascript中最常用的继承模式。
原型式继承的思想是基于已有的对象创建新的对象,并添加自己需要的其他属性,而不必创建自定义类型。
function object(o) { function F(){}; F.prototype = o; return new F();
} // 即对传入的对象执行了一次浅复制,ES6添加了Object.create()方法来规范这中继承方式 const person = {
name: 'Michael',
friends: ["aa", "bb"]
} const anotherPerson = Object.create(person, {
name: {
value: 'John' }
}); alert(anotherPerson.name); // "John"
这种继承方式的所有实例也都会共享引用属性的值。
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装过程的函数,该函数在内部以某种方式来增强对象,最后返回这个对象。
function createAnother(original) { const clone = new Object(original); // 这里可以换成其他自定义的生成对象的方法,如果是浅拷贝,则同样会共享引用类型的对象 clone.sayHi = function() { alert("Hi");
}; return clone;
} const person = {
name: 'Michael',
friends: ["aa", "bb"]
}; const anotherPerson = createAnother(person); anotherPerson.sayHi(); // "Hi"
前面所述的组合继承的最大问题就是会调用两次超类型构造函数,一次是创建子类原型的时候,另一次是在子类型构造函数内部。其结果是子类型的实例属性屏蔽了它的原型属性,原型上依然存在着相应的属性。
寄生组合式继承的基本思路是使用寄生式继承来继承超类的原型,然后再将结果指定给子类型的原型
function inheritPrototype(subType, superType) { const prototype = new Object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype;
} 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); this.age = age;
} inheritPrototype(SubType, superType); SubType.prototype.sayAge = function () { alert(this.age);
};