js继承
原型链继承
子类原型指向父类的一个实例对象。子类会继承父类实例上的属性和方法,也可以访问父类原型上的属性和方法。
如果子类原型上想要覆盖父类的方法/添加父类没有的方法,这些方法必须在原型赋值之后添加,否则无效。
function SuperType () {
this.prop = true;
this.colors = ['red', 'green', 'blue'];
}
SuperType.prototype.getSuperValue = function () {
return this.prop;
}
function SubType () {
this.subProp = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
// 新方法
SubType.prototype.getSubValue = function () {
return this.subProp;
}
// 覆盖已有方法
SubType.prototype.getSuperValue = function () {
return false;
}
const instance = new SubType();
console.log(instance.getSuperValue()); // false
const instance2 = new SubType();
instance2.colors.push('black');
console.log(instance2.colors); // "red,green,blue,black"
const instance3 = new SubType();
console.log(instance3.colors); // "red,green,blue,black"
console.log(SubType.prototype.constructor === SuperType); // true
缺点
- 如果父类实例上存在引用属性,子类的所有实例对象都会共享父类实例上的引用属性(父类实例属性 => 子类原型属性)
- 创建子类实例对象时不能给父类构造函数传递参数
- 重写原型导致constructor丢失
盗用构造函数继承
子类继承父类实例属性和方法,可以在创建子类实例时给父类构造函数传递参数。
function SuperType (name) {
this.name = name;
this.colors = ['red', 'green', 'blue'];
this.sayYes = function () {
console.log('yes');
}
}
SuperType.prototype.sayNo = function () {
console.log('no');
}
function SubType () {
// 继承SuperType
SuperType.call(this, 'JavaScript');
this.age = 30;
}
const instance = new SubType();
instance.colors.push('black');
console.log(instance.colors); // "red,green,blue,black"
const instance2 = new SubType();
console.log(instance2.colors); // "red,green,blue"
const instance3 = new SubType();
console.log(instance3.name); // "JavaScript"
console.log(instance3.age); // 30
console.log(instance instanceof SubType); // true
console.log(instance instanceof SuperType); // false
console.log(SubType.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // false
instance.sayYes() // yes
console.log(instance.sayNo); // undefined
优点
- 避免了父类实例上的引用属性被子类所有实例对象共享
缺点
- 最多只能继承父类在构造函数中定义的属性和方法
- 不能访问父类原型上定义的属性和方法
- 没有保留
instanceof
操作符和isPtototypeOf()
方法识别合成对象的能力
组合继承
原型链继承 + 盗用构造函数继承
-
原型链继承:继承父类原型上的属性和方法
-
盗用构造函数继承:继承父类实例上的属性
function SuperType (name) {
this.name = name;
this.colors = ['red', 'green', 'blue'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
}
function SubType (name, age) {
// 继承SuperType上的属性
SuperType.call(this, name);
this.age = age;
}
// 继承父类原型上的属性和方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function () {
console.log(this.age);
}
const instance = new SubType('JavaScript', 30);
instance.colors.push('black');
console.log(instance.colors); // "red,green,blue,black"
instance.sayName(); // "JavaScript"
instance.sayAge(); // 30
const instance2 = new SubType('ECMA Script', 40);
console.log(instance2.colors); // "red,green,blue"
instance2.sayName(); // "ECMA Script"
instance2.sayAge(); // 40
console.log(SubType.prototype.constructor === SuperType); // true
优点
- 可以继承父类上的原型和方法
- 避免了父类实例上的引用属性被子类所有实例对象共享
缺点
- 创建子类实例对象时,父类构造函数总是会被调用两次(效率问题)
- 重写原型导致constructor丢失
原型式继承
不自定义类型也可以通过原型实现对象之间的信息共享。
适用于不要单独创建构造函数。在一个对象的基础上创建一个新对象
ES6中与Object.create()
方法效果相同
function object (o) {
function F () {}
F.prototype = o;
return new F();
}
const person = {
name: 'Script',
colors: ['red', 'green', 'blue']
};
const person2 = object(person);
person2.name = 'JavaScript';
person2.colors.push('black');
const person3 = object(person);
person3.name = 'ECMA Script';
person3.colors.push('yellow');
console.log(person.colors); // "red,green,blue,black,yellow"
缺点
- 关于继承的引用属性同原型链继承一致
寄生式继承
与原型式继承
相似
思路:
- 创建一个实现继承的函数,接收一个对象作为参数
- 在函数中以某种方法增强对象参数,返回这个对象
function object (o) {
function F () {}
F.prototype = o;
return new F();
}
function createAnother(original) {
const clone = object(original);
// 增强对象
clone.sayHi = function () {
console.log('hi');
}
return clone;
}
缺点
- 给对象添加函数会导致函数难以复用
寄生式组合继承
盗用构造函数继承属性 + 混合式原型链继承属性和方法
盗用构造函数继承 + 寄生式继承
思路:
- 寄生式继承方式继承父类原型
- 将返回的父类原型副本赋值给子类原型
// 继承父类原型属性和方法
function inheritPrototype(subType, superType) {
const protoType = object(superType.prototype); // 创建父类原型副本对象
protoType.constructor = subType;
subType.prototype = protoType;
}
function SuperType (name) {
this.name = name;
this.colors = ['red', 'green', 'blue'];
this.sayYes = function () {
console.log('yes');
}
}
SuperType.prototype.sayNo = function () {
console.log('no');
}
function SubType (name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
const instance = new SubType('JavaScript', 30);
instance.sayYes(); // yes
instance.sayNo(); // no
优点
- 可以继承父类上的原型和方法
- 避免了父类实例上的引用属性被子类所有实例对象共享
- 创建子类实例对象时,父类构造函数仅被调用一次