摘自杰瑞 · 赫尔曼的《你好,多莉》歌曲: 一天不学就没手感了,三天不学就不爱学了,两礼拜不学之前学的全忘了. ——B站 Zhan

Js继承方式总结

内容目录

JS的继承方式总结

JavaScript 继承机制主要有以下几种方式,每种方式都有其优缺点:

1. 原型链继承(Prototype Chain Inheritance)

原理:
通过将子类的原型(prototype)指向父类的实例来实现继承。

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

Parent.prototype.sayHello = function() {
  console.log('Hello from parent');
};

function Child() {
  this.age = 18;
}

Child.prototype = new Parent();

const child = new Child();
console.log(child.name); // 'parent'
child.sayHello(); // 'Hello from parent'

//缺点
function Parent() {
    this.arr = [1, 2, 3];
}

Parent.prototype.getArr = function() {
    return this.arr;
}

function Child() {}

Child.prototype = new Parent();

var child1 = new Child();
var child2 = new Child();
child1.arr.push(4);

console.log(child1.arr); // [1, 2, 3, 4]
console.log(child2.arr); // [1, 2, 3, 4] - 共享引用属性,修改影响所有实例

优点:

  • 简单易懂,便于实现。

缺点:

  • 所有子类实例共享父类实例的引用属性,修改一个实例的引用属性会影响所有实例。
  • 无法向父类构造函数传参。
  • 创建子类实例时无法独立调用父类构造函数。

2. 借用构造函数继承(Constructor Stealing, also called Classical Inheritance or Object Borrowing)

原理:
在子类构造函数中调用父类构造函数,使用callapply方法。

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

function Child(name, age) {
  Parent.call(this, name); // 借用父类的构造函数
  this.age = age;
}

const child = new Child('child', 18);
console.log(child.name); // 'child'
console.log(child.age); // 18

//改正原型链的缺点
function Parent() {
    this.arr = [1, 2, 3];
}
Parent.prototype.getArr = function() {
    return this.arr;
}
function Child() {
    Parent.call(this); // 借用构造函数
}

var child1 = new Child();
var child2 = new Child();
child1.arr.push(4);
console.log(child1.arr); // [1, 2, 3, 4]
console.log(child2.arr); // [1, 2, 3]
console.log(child2.getArr); //undifine

优点:

  • 解决了原型链继承中引用属性共享的问题。
  • 可以向父类构造函数传参。

缺点:

  • 父类的方法无法复用,每次创建子类实例都会重新创建父类的方法。
  • 不能继承父类的prototype属性。

3. 组合继承(Combination Inheritance)

原理:
结合原型链继承和借用构造函数继承的优点,既能继承父类的实例属性,又能继承父类的原型方法。

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

Parent.prototype.sayHello = function() {
  console.log('Hello from parent');
};

function Child(name, age) {
  Parent.call(this, name); // 借用构造函数继承属性
  this.age = age;
}

Child.prototype = new Parent(); // 原型链继承方法

const child = new Child('child', 18);
console.log(child.name); // 'child'
console.log(child.age); // 18
child.sayHello(); // 'Hello from parent'

优点:

  • 结合了两者的优点,既能传参又能复用方法。
  • 圆形属性不会被共享,可以继承父类的原型链上的属性和方法。

缺点:

  • 调用了两次父类构造函数,第一次是在创建子类原型时,第二次是在子类构造函数中。

4. 原型式继承(Prototypal Inheritance)

原理:
利用一个空对象作为中介,将某个对象直接赋值给空对象的原型。

function createObject(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

const parent = {
  name: 'parent',
  sayHello: function() {
    console.log('Hello from parent');
  }
};

const child = createObject(parent);
child.age = 18;

console.log(child.name); // 'parent'
child.sayHello(); // 'Hello from parent'
console.log(child.age); // 18

优点:

  • 简单,直接利用现有对象创建新对象。

缺点:

  • 与原型链继承类似,引用属性共享问题依然存在。

5. 寄生式继承(Parasitic Inheritance)

原理:
在原型式继承的基础上,增强对象,返回构造函数。

function createObject(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

function createEnhancedObject(original) {
  const clone = createObject(original);
  clone.sayHi = function() {
    console.log('Hi from child');
  };
  return clone;
}

const parent = {
  name: 'parent',
  sayHello: function() {
    console.log('Hello from parent');
  }
};

const child = createEnhancedObject(parent);
child.age = 18;

console.log(child.name); // 'parent'
child.sayHello(); // 'Hello from parent'
child.sayHi(); // 'Hi from child'
console.log(child.age); // 18

优点:

  • 在原型式继承的基础上增强对象功能。

缺点:

  • 与原型式继承类似,引用属性共享问题依然存在。

6. 寄生组合式继承(Parasitic Combination Inheritance)

原理:
通过寄生方式,去掉父类实例的属性,解决组合继承中父类构造函数被调用两次的问题。

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

Parent.prototype.sayHello = function() {
  console.log('Hello from parent');
};

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

function inheritPrototype(child, parent) {
  const prototype = Object.create(parent.prototype); // 创建对象
  prototype.constructor = child; // 增强对象
  child.prototype = prototype; // 指定对象
}

inheritPrototype(Child, Parent);

const child = new Child('child', 18);
console.log(child.name); // 'child'
console.log(child.age); // 18
child.sayHello(); // 'Hello from parent'

优点:

  • 最优的继承方式,避免了组合继承中的缺点,充分发挥了两者的优点。
  • 原型属性不会被共享。
  • 可以继承父类的原型链上的属性和方法。
  • 只调用一次Parent()。因此他不会在Child的prototype上添加Parent的属性和方法。

缺点:

  • 相对复杂,理解和实现起来需要更多的代码。
  • Child.prototype的原始属性和方法会丢失。

7. ES6 类继承(Class Inheritance in ES6)

原理:
使用classextends关键字,通过类语法糖实现继承。

class Parent {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log('Hello from parent');
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 调用父类构造函数
    this.age = age;
  }
}

const child = new Child('child', 18);
console.log(child.name); // 'child'
console.log(child.age); // 18
child.sayHello(); // 'Hello from parent'

优点:

  • 语法简洁,易于理解和使用。
  • 继承机制更加清晰直观。

缺点:

  • 仍然基于原型链,存在某些情况下性能问题。
  • 部分浏览器和环境可能不支持(需要使用 Babel 等工具进行转译)。

每种继承方式都有其应用场景和适用条件,选择合适的继承方式可以根据项目的具体需求和情况来决定。

发表评论