JS实现继承

251 阅读2分钟

原型链继承

实现:子类 prototype 赋值为 父类 instance (实例)。

function Parent() {
    this.names = ['kevin', 'daisy'];
}

function Child() {}
// 子类 prototype 赋值为 父类 instance (实例)。
Child.prototype = new Parent();
// 手动修改 prototype.constructor = 子类 
Child.prototype.constructor = Child

缺点:

  • 所有子类实例,共享可修改的原型属性。
  • 子类 new 时不能向 父类 构造函数 传参。
  • 要手动修改 prototype.constructor 为子类。
let a = new Child();

a.names.push('yayu');

console.log(a.names); // ["kevin", "daisy", "yayu"]

let b = new Child();

console.log(b.names); // ["kevin", "daisy", "yayu"]

借用构造函数(经典继承)

实现:子类 调用 父类构造函数。

function Parent() {
    this.names = ['1', '2'];
    this.getNames = () => this.names;
}

Parent.prototype.protoGetNames = function () {}

function Child() {
    // 子类 调用 父类构造函数。
    Parent.call(this);
}

优点:

  • 属性定义在 this,避免共享。
  • 可以向 父类 构造函数 传参。

缺点:

  • 父类方法,不能通过 prototype 访问。
  • 方法必须在父类构造函数中定义,且每次创建实例都会创建一遍方法。
// a
let a = new Child();

a.names.push('3');

console.log(a.names); // ["1", "2", "3"]
// b
let b = new Child();

console.log(b.names); // ["1", "2"]
// 方法都在构造函数中定义
console.log(a.getNames()) // [ '1', '2', '3' ]
// 每次创建实例都会创建一遍方法。
console.log(a.getNames === b.getNames) // false
// 访问不到父类原型方法
console.log(a.protoGetNames)// undefined

组合继承

实现:原型链继承 + 构造函数继承

function Parent(name) {
    this.name = name;
}

Parent.prototype.getName = function () {
    return this.name
}

function Child(name, age) {
    // 构造函数继承
    Parent.call(this, name);
    this.age = age;
}
// 原型继承
//                父类构造函数重复调用,原型有了父类属性和方法
Child.prototype = new Parent();
Child.prototype.constructor = Child;

优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。 缺点:

  • 调用2次构造函数。
  • 原型上有父类属性和方法。

寄生式继承 (对象增强)

实现:一个特定封装继承的对象,增强对象,类似于继承。

// 主要关注对象,而不在乎类型和构造函数的场景。-- 红宝书
function createDog(obj){
    // object()函数不是寄生式继承所必需的,任何返回新对象的函数都可以在这里使用。 -- 红宝书
    let clone = object(obj);
    clone.getColor = function(){
        console.log(clone.color)
    }
    return clone
}

// 寄生继承就是不用实例化父类了,直接实例化一个临时副本实现了相同的原型链继承。
function object(proto) {
    function F() {}
    F.prototype = proto;
    return new F();
}

优点:不调用构造函数。 缺点:

  • 特定封装,无法复用。
let dog = {
    color: 'yellow'
}

let dog1 = createDog(dog);
dog1.getColor();  // yellow

寄生 组合 继承

实现:寄生 + 组合

function Parent(name) {
   this.name = name;
}

Parent.prototype.getName = function () {
  return this.name
}

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}

// 寄生组合继承
function extend(Child, Parent) {
  var F = function () {};
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  // 修正constructor
  Child.prototype.constructor = Child
}

extend(Child, Parent)

优点:几乎完美实现继承,修正了组合继承的原型bug。

  • 属性定义在 this,避免共享。
  • 可以向父类传参。
  • 只调用一次构造函数,原型无多余属性。
  • construcot 正确。