Javascript继承
louzhedong opened this issue · comments
Javascript的继承和传统的OO语言不一样,有一些特别的地方需要我们去注意。文章作为阅读红皮书(JS高程)的笔记,记录Javascript语言中的继承方式。
1.原型链继承
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就是一个会被所有实例共享的对象。
原型继承的另一个缺点是在创建子类的实例时,不能向超类的构造函数传递参数。
2.借用构造函数(经典继承)
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
但借用改造函数继承也有它的缺点,即无法避免构造函数模式存在的函数复用的问题,因为所有的方法都在构造函数中定义。
3.组合继承(伪经典继承)
组合继承将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
思路是使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。
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中最常用的继承模式。
4.原型式继承
原型式继承的**是基于已有的对象创建新的对象,并添加自己需要的其他属性,而不必创建自定义类型。
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"
这种继承方式的所有实例也都会共享引用属性的值。
5.寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装过程的函数,该函数在内部以某种方式来增强对象,最后返回这个对象。
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"
6.寄生组合式继承
前面所述的组合继承的最大问题就是会调用两次超类型构造函数,一次是创建子类原型的时候,另一次是在子类型构造函数内部。其结果是子类型的实例属性屏蔽了它的原型属性,原型上依然存在着相应的属性。
寄生组合式继承的基本思路是使用寄生式继承来继承超类的原型,然后再将结果指定给子类型的原型
function inheritPrototype(subType, superType) {
// 创建超类的一个副本
const prototype = new Object(superType.prototype);
// 为创建的副本添加constructor属性,弥补因重写原型而失去的默认的constructor属性
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);
};