Js是通过原型链实现继承的,有以下几种方式:
1、原型链继承:
方式:子类 的 prototype 属性指向 父类 的 一个实例
局限:
(1)父类实例 的 实例属性 会变成 下方原型链 所有实例 的 原型属性
(2)创建子类实例时,不能自定义源自父类的实例属性,源自父类的实例属性只能采用默认值
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.sub_property = false;
}
// SubType继承自SuperType, 继承完再给原型添加方法
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.sub_property;
}
let instance = new SubType();
console.log(instance.getSuperValue(), instance.getSubValue());
// true false
纯原型链继承的方式存在缺陷:
在JS中,一个实例有两种属性,实例属性、原型属性,可以理解为 实例属性为实例私有的,原型属性为同一个原型出来的实例中共享的属性。
但如果采用纯原型链继承的方式,SuperType的实例属性将直接变成SubType的原型属性,那么就会导致一些问题
如 小鸡,小鸭子都是继承自鸟类,鸟类有个实例属性 skills = [], 如果用纯原型链的方式继承的话,那么skills将成为禽类的原型方法,那么,当小鸡会喔喔叫的时候,小鸭子也会。当小鸭子会游泳的时候,小鸡也会,这就是缺陷。
假定没有上面这个问题,我们采用原型链继承的方式也不能在初始化小鸭子,小鸡的时候定制skills,如何解决呢?这引出了下面的盗用构造函数方法
2、盗用构造函数继承
// 盗用构造函数实现继承
function SuperType() {
this.skills = [];
}
function SubType() {
// 盗用父类构造函数
SuperType.call(this);
}
const hen = new SubType();
const duck = new SubType();
hen.skills.push("jiji");
duck.skills.push("swim");
console.log(hen.skills, duck.skills);
// [ 'jiji' ] [ 'swim' ]
方式:在子类构造函数中使用 call 调用 父类构造函数 给this添加自定义的父类实例属性
缺陷:
盗用构造函数成功解决了 子类创建实例时无法自定义源自父类的实例属性 这个问题!
但这样做无法继承到父类的原型属性,有什么好办法?
答案是配合原型链继承一起使用。
我们会发现,盗用构造函数后,子类实例中的父类实例属性 会 遮蔽 SubType.protoype = new SuperType();
中的实例属性。
因此只能继承到 原型方法!
这就是 组合继承
3、组合继承
// 组合继承
function SuperType(name) {
this.name = name;
this.skills = [];
}
SuperType.prototype.sayName = function() {
return this.name;
}
function SubType(name, color) {
// 继承父类的实例属性
SuperType.call(this, name);
this.color = color;
}
// 使用原型链继承原型属性
SubType.prototype = new SuperType();
SubType.prototype.sayColor = function() {
return this.color;
}
const hen = new SubType("hen", "red");
const duck = new SubType("duck", "yellow");
hen.skills.push("wowo");
duck.skills.push("swim");
console.log(hen.skills, duck.skills); // [ 'wowo' ] [ 'swim' ]
console.log(hen.sayName(), duck.sayName()); // hen duck
console.log(hen.sayColor(), duck.sayColor()); // red yellow
实现:原型链继承 + 盗用构造函数遮蔽父类实例的实例属性
缺陷:调用了 2 次父类构造函数
如何优化?
道格拉斯提出了原型式集成,也就是我们现在常用的 let res = Object.create(obj);
res 的 _proto_ 指向 obj 从而实现了无需父类构造函数的 对象层面上的继承
实现机制类似以下代码:
function object(obj) {
function fn() {};
fn.prototype = obj;
return new fn();
}
而寄生式继承是在原型式继承上提出的扩充,在 实现 对象继承 后再给新对象添加属性、方法等,最后返回一个 有新属性、新方法的 新对象。
let bird = {
name: "normal bird",
skills: []
};
function createDuck(obj) {
let duck = Object.create(obj);
duck.swim = () => console.log("duck can swim.");
duck.skills.push("swim");
return duck;
}
const duck = createDuck(bird);
console.log(duck, duck.skills); // { swim: [Function] } [ 'swim' ]
duck.swim(); // duck can swim.
这样,我们就能引出我们的优化方案:寄生式组合继承
4、寄生式组合继承
实例属性仍然使用盗用构造函数进行继承
原型属性使用寄生式继承实现,而非组合继承中 SubType.prototype = new SuperType();
这样改进解决了组合继承的什么问题呢?
组合继承使用 new SuperType() 的方式,导致了会调用两次父类构造函数,这样有效率问题。另外,SubType原型上会有不必要的SuperType实例属性。
而寄生式组合继承 只需要在盗用构造函数时只调用一次父类构造函数,而且Subtype的原型仅有SuperType原型中的属性。
function SuperType(name) {
this.name = name;
this.skills = [];
}
// 这里不能使用 箭头函数 否则返回undefined 箭头函数作用域中无this,像上一层作用域,也就是全局作用域查找 this.name 无
SuperType.prototype.sayName = function() {
return this.name;
}
const bird = new SuperType("bird");
console.log(bird, bird.sayName()); // SuperType { name: 'bird', skills: [] } bird
function SubType(name, age) {
// 盗用构造函数 以继承父类构造函数的实例方法
SuperType.call(this, name);
this.age = age;
}
function inheritPrototype(Sub, Super) {
let prototype = Object.create(Super.prototype);
prototype.constructor = Sub;
Sub.prototype = prototype;
}
// 使用寄生式继承 完成 对父类原型属性的继承
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
return this.age;
}
const duck = new SubType("duck", 8);
console.log(duck, duck.sayName(), duck.sayAge()); //SubType { name: 'duck', skills: [], age: 8 } duck 8
寄生式组合继承是最佳的继承方式!
5、es6继承语法糖
方式:使用 extends 单继承,使用 super(参数列表) 盗用构造函数,super() 要放在最前面
class Super() {}
class Sub() {
constructor() {
super();
this.a = 1;
}
}
实现抽象类,不可直接构造:
class Super() {
if(new.target === Super) throw new Error("Super cannout be instanced directly");
}