继承

1. 原型链继承

核心思想:

  • 通过将子类的原型指向父类的实例,实现继承。

  • 子类可以访问父类原型上的属性和方法。

代码示例:

function Parent() {
  this.name = "Parent";
}
Parent.prototype.sayName = function () {
  console.log(this.name);
};

function Child() {}
Child.prototype = new Parent(); // 原型链继承

const child = new Child();
child.sayName(); // 输出: Parent

优点:

  • 简单易用,直接利用原型链特性。

缺点:

  • 所有子类实例共享父类实例的引用属性(如数组、对象),容易造成数据污染。

  • 无法向父类构造函数传参。

记忆技巧:

  • 原型链:想象一条链子,子类通过 prototype 连接到父类的实例。

  • 共享问题:记住“共享”是优点也是缺点,适合方法继承,不适合属性继承。


2. 借用构造函数继承

核心思想:

  • 在子类构造函数中调用父类构造函数,通过 callapply 实现属性继承。

  • 解决了原型链继承中引用属性共享的问题。

代码示例:

function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}

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

const child1 = new Child("Child1");
child1.colors.push("green");
console.log(child1.colors); // 输出: ['red', 'blue', 'green']

const child2 = new Child("Child2");
console.log(child2.colors); // 输出: ['red', 'blue']

优点:

  • 解决了引用属性共享的问题。

  • 可以向父类构造函数传参。

缺点:

  • 无法继承父类原型上的方法。

  • 方法都在构造函数中定义,无法复用。

记忆技巧:

  • 借用:想象子类“借用”了父类的构造函数来初始化自己的属性。

  • 无法复用:记住方法无法复用,每次创建实例都会重新定义方法。


3. 组合继承

核心思想:

  • 结合原型链继承和借用构造函数继承。

  • 使用借用构造函数继承属性,使用原型链继承方法。

代码示例:

function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}
Parent.prototype.sayName = function () {
  console.log(this.name);
};

function Child(name) {
  Parent.call(this, name); // 借用构造函数继承属性
}
Child.prototype = new Parent(); // 原型链继承方法

const child1 = new Child("Child1");
child1.colors.push("green");
console.log(child1.colors); // 输出: ['red', 'blue', 'green']
child1.sayName(); // 输出: Child1

const child2 = new Child("Child2");
console.log(child2.colors); // 输出: ['red', 'blue']
child2.sayName(); // 输出: Child2

优点:

  • 解决了引用属性共享和方法复用的问题。

  • 可以向父类构造函数传参。

缺点:

  • 调用了两次父类构造函数(一次在 Parent.call,一次在 new Parent()),性能略有损耗。

记忆技巧:

  • 组合:想象把“借用构造函数”和“原型链”两种方式组合在一起。

  • 两次调用:记住父类构造函数被调用了两次。


4. 寄生式继承

核心思想:

  • 基于一个对象创建新对象,并增强新对象的能力。

  • 类似于工厂模式,常用于对象增强。

代码示例:

function createAnother(original) {
  const clone = Object.create(original); // 基于原对象创建新对象
  clone.sayHi = function () {
    // 增强新对象
    console.log("Hi");
  };
  return clone;
}

const parent = { name: "Parent" };
const child = createAnother(parent);
child.sayHi(); // 输出: Hi
console.log(child.name); // 输出: Parent

优点:

  • 简单灵活,适合对象增强。

缺点:

  • 方法无法复用,每次创建对象都会重新定义方法。

记忆技巧:

  • 寄生:想象新对象“寄生”在原对象上,并增强了功能。

  • 工厂模式:记住它类似于工厂模式,用于创建增强对象。


5. class 实现继承

核心思想:

  • 使用 ES6 的 classextends 关键字实现继承。

  • 语法更简洁,底层仍然是基于原型链。

代码示例:

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

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

const child = new Child("Child");
child.sayName(); // 输出: Child

优点:

  • 语法简洁,易于理解。

  • 底层自动处理原型链和构造函数调用。

缺点:

  • 需要支持 ES6 的环境。

记忆技巧:

  • class 和 extends:记住这是 ES6 的语法糖,底层仍然是原型链。

  • super:记住 super 用于调用父类构造函数。


6. 寄生组合式继承

核心思想:

  • 结合组合继承和寄生式继承。

  • 通过 Object.create() 创建父类原型的副本,避免调用两次父类构造函数。

代码示例:

function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}
Parent.prototype.sayName = function () {
  console.log(this.name);
};

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

// 寄生组合式继承
Child.prototype = Object.create(Parent.prototype); // 创建父类原型的副本
Child.prototype.constructor = Child; // 修复构造函数指向

const child1 = new Child("Child1");
child1.colors.push("green");
console.log(child1.colors); // 输出: ['red', 'blue', 'green']
child1.sayName(); // 输出: Child1

const child2 = new Child("Child2");
console.log(child2.colors); // 输出: ['red', 'blue']
child2.sayName(); // 输出: Child2

优点: 解决了组合继承中调用两次父类构造函数的问题。

是 JavaScript 中最理想的继承方式。

记忆技巧: 寄生组合:想象“寄生”在父类原型上,同时“组合”了借用构造函数的方式。

总结与记忆技巧

  1. 原型链继承:链式连接,共享问题。

  2. 借用构造函数继承:借用父类构造函数,无法复用方法。

  3. 组合继承:组合两种方式,调用两次构造函数。

  4. 寄生式继承:对象增强,工厂模式。

  5. class 继承:ES6 语法糖,简洁易用。

  6. 寄生组合式继承:组合继承的优化版本,最优继承方式。

最后更新于

这有帮助吗?