继承的 6 种实现方式 - 转

  1. ES5 继承
    1. 1 原型链继承
    2. 2 构造函数内继承
    3. 3 组合继承
    4. 4 寄生组合继承
  2. ES6 继承

转自 : 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'); 这样的语句之后执行。
缺点:

  1. 无法多继承
  2. 父类的所有属性被子类所有实例共享,一个子类实例修改,其他子类属性都可以看到。
  3. 创建子类实例时,无法向父类构造函数传参

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());  // 报错

优点:相比直接的原形链继承,解决了:多继承问题,父类传递参数问题,子类实例对象之间共享父类属性问题
缺点:

  1. 由于没有使用原型,所以不能继承原型的属性和方法。
  2. 每个子类都有父类实例函数的副本,影响性能

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 会得到两个属性 namecolors;当调用子类 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() { }
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。
我的空间