转自 : https://github.com/sisterAn/blog/issues/41
ES5 继承
先定义一个父类:
function SuperType () {
// 属性
this.name = 'SuperType';
}
// 原型方法
SuperType.prototype.sayName = function() {
return this.name;
};
1 原型链继承
做法:子类的原型直接被赋值为一个父类的实例
// 父类
function SuperType () {
this.name = 'SuperType'; // 父类属性
}
SuperType.prototype.sayName = function () { // 父类原型方法
return this.name;
};
// 子类
function SubType () {
this.subName = "SubType"; // 子类属性
};
SubType.prototype = new SuperType(); // 重写原型对象,代之以一个新类型的实例
// 这里实例化一个 SuperType 时, 实际上执行了两步
// 1,新创建的对象复制了父类构造函数内的所有属性及方法
// 2,并将原型 __proto__ 指向了父类的原型对象
SubType.prototype.saySubName = function () { // 子类原型方法
return this.subName;
}
// 子类实例
let instance = new SubType();
// instanceof 通过判断对象的 prototype 链来确定对象是否是某个类的实例
instance instanceof SubType; // true
instance instanceof SuperType; // true
// 注意这里
SubType instanceof SuperType; // false
SubType.prototype instanceof SuperType ; // true
优点: 继承了父类的构造函数模板中的属性,又继承了父类的原型对象。
子类要新增原型属性和方法,则必须放在 SubType.prototype = new SuperType('SubType');
这样的语句之后执行。
缺点:
- 无法多继承
- 父类的所有属性被子类所有实例共享,一个子类实例修改,其他子类属性都可以看到。
- 创建子类实例时,无法向父类构造函数传参
2 构造函数内继承
做法:在子类型的构造函数内部调用父类型构造函数。
没有用到原型。只是把父类的实例属性赋值给子类
// 父类
function SuperType (name) {
this.name = name; // 父类属性
}
SuperType.prototype.sayName = function () { // 父类原型方法
return this.name;
};
// 子类
function SubType () {
// 调用 SuperType 构造函数
SuperType.call(this, 'SuperType'); // 在子类构造函数中,向父类构造函数传参
// 为了保证子父类的构造函数不会重写子类的属性,需要在调用父类构造函数后,定义子类的属性
this.subName = "SubType"; // 子类属性
};
// 子类实例
let instance = new SubType(); // 运行子类构造函数,并在子类构造函数中运行父类构造函数,this 绑定到子类
console.log(instance instanceof SuperType); // false, 子类实例并不是父类的实例,只是子类的实例
console.log(instance.sayName()); // 报错
优点:相比直接的原形链继承,解决了:多继承问题,父类传递参数问题,子类实例对象之间共享父类属性问题
缺点:
- 由于没有使用原型,所以不能继承原型的属性和方法。
- 每个子类都有父类实例函数的副本,影响性能
3 组合继承
将上面的两个,原型链继承与构造函数继承组合在一起
做法:使用原型链继承使用对原型属性和方法的继承,通过构造函数继承来实现对实例属性的继承。这样既能通过在原型上定义方法实现函数复用,又能保证每个实例都有自己的属性。
// 父类
function SuperType (name) {
this.colors = ["red", "blue", "green"];
this.name = name; // 父类属性
}
SuperType.prototype.sayName = function () { // 父类原型方法
return this.name;
};
// 子类
function SubType (name, subName) {
// 调用 SuperType 构造函数
SuperType.call(this, name); // ---- 第二次调用 SuperType----
this.subName = subName;
};
// ---- 第一次调用 SuperType----
SubType.prototype = new SuperType(); // 重写原型对象,代之以一个新类型的实例
SubType.prototype.constructor = SubType; // 组合继承需要修复构造函数指向
SubType.prototype.saySubName = function () { // 子类原型方法
return this.subName;
}
// 子类实例
let instance = new SubType('An', 'sisterAn')
instance.colors.push('black')
console.log(instance.colors) // ["red", "blue", "green", "black"]
instance.sayName() // An
instance.saySubName() // sisterAn
let instance1 = new SubType('An1', 'sisterAn1')
console.log(instance1.colors) // ["red", "blue", "green"]
instance1.sayName() // An1
instance1.saySubName() // sisterAn1
SubType.prototype = new SuperType()
这行代码调用父类 SuperType 构造函数时,子类的原型 SubType.prototype
会得到两个属性 name
和 colors
;当调用子类 SubType
构造函数时,会第二次调用父类 SuperType 构造函数,这一次又在新对象属性上在再次创建了名为 name 和 colors 的属性,这两个属性就会屏蔽前面子类的原型对象上的同名属性。
// instanceof:instance 的原型链是针对 SuperType.prototype 进行检查的
instance instanceof SuperType // true
instance instanceof SubType // true
// isPrototypeOf:instance 的原型链是针对 SuperType 本身进行检查的
SuperType.prototype.isPrototypeOf(instance) // true
SubType.prototype.isPrototypeOf(instance) // true
优点:可继承原型的属性和方法,也可继承实例属性和方法,不存在属性共享,父类可以传参
缺点:调用了两次父类构造函数,虽然够杂子类实例时,将原型上的属性同名属性覆盖
4 寄生组合继承
组合继承中,调用了两次父类构造函数,这里 通过通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法 / 属性,避免的组合继承的缺点。
做法:借用 构造函数 继承 属性 ,通过 原型链的混成形式 来继承 方法
// 父类
function SuperType (name) {
this.colors = ["red", "blue", "green"];
this.name = name; // 父类属性
}
SuperType.prototype.sayName = function () { // 父类原型方法
return this.name;
};
// 子类
function SubType (name, subName) {
// 调用 SuperType 构造函数
SuperType.call(this, name); // ---- 第二次调用 SuperType,继承实例属性 ----
this.subName = subName;
};
// ---- 第一次调用 SuperType,继承原型属性 ----
SubType.prototype = Object.create(SuperType.prototype)
SubType.prototype.constructor = SubType; // 注意:增强对象
let instance = new SubType('An', 'sisterAn')
console.log(Son.prototype.__proto__ == Father.prototype); // true
弥补了组合继承的缺点。注意和组合继承比较一下,子类原型的 [[Prototype]]
是谁
ES6 继承
class People {
constructor(name) {
this.name = name
}
run() { }
}
// extends 相当于方法的继承
// 替换了上面的 3 行代码
class Man extends People {
constructor(name) {
// super 相当于属性的继承
// 替换了 People.call(this, name)
super(name)
this.gender = '男'
}
fight() { }
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。