JavaScript 中实现继承的方式

JavaScript 中实现继承有多种方法。

2024-06-13
2024-10-09

JavaScript 中实现继承有多种方法,但是归根到底就是通过原型链机制实现。

原型链继承(Prototype Chain Inheritance)

原型链继承是最基本的继承方式,通过将子类的原型指向父类的实例来实现继承。

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

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

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

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

const child = new Child();
console.log(child.name); // 输出: Parent
child.sayHello(); // 输出: Hello from Parent

缺点:父类构造函数中的引用类型(比如对象/数组),会被所有子类实例共享。其中一个子类实例进行修改,会导致所有其他子类实例的这个值都会改变。

借用构造函数继承(Constructor Stealing Inheritance)

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

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

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

const child = new Child('Child', 10);
console.log(child.name); // 输出: Child
console.log(child.age); // 输出: 10

优点:解决了原型链继承中构造函数引用类型共享的问题,同时可以向构造函数传参(通过call传参)。

缺点:所有方法都定义在构造函数中,每次都需要重新创建(对比原型链继承的方式,方法直接写在原型上,子类创建时不需要重新创建方法)。

组合继承(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();
Child.prototype.constructor = Child;

const child = new Child('Child', 10);
console.log(child.name); // 输出: Child
console.log(child.age); // 输出: 10
child.sayHello(); // 输出: Hello from Parent

优点:同时解决了构造函数引用类型的问题,同时避免了方法会被创建多次的问题。

缺点:父类构造函数被调用了两次。同时子类实例以及子类原型对象上都会存在 name 属性。虽然根据原型链机制,并不会访问到原型对象上的同名属性,但总归是不美。

原型式继承(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);
console.log(child.name); // 输出: Parent
child.sayHello(); // 输出: Hello from Parent

或者使用 Object.create 方法来创建一个子类对象。

let animal = {
  type: "animal",
  speak: function () {
    console.log(this.name + " makes a noise.");
  },
};
let dog = Object.create(animal);
dog.name = "Rex";

寄生式继承(Parasitic Inheritance)

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

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

function createEnhancedObject(o) {
  const clone = createObject(o);
  clone.sayGoodbye = function() {
    console.log('Goodbye from Child');
  };
  return clone;
}

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

const child = createEnhancedObject(parent);
console.log(child.name); // 输出: Parent
child.sayHello(); // 输出: Hello from Parent
child.sayGoodbye(); // 输出: Goodbye from Child

或者

function createDog(name) {
  let dog = Object.create(animal);
  dog.name = name;
  return dog;
}

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

寄生组合式继承是一种结合了寄生式继承和组合式继承的方式,旨在减少组合式继承中多余的调用父类构造函数的开销。通过创建一个中间对象来继承父类的原型,避免了调用父类构造函数两次。

// 父类构造函数
function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

// 父类原型方法
Parent.prototype.sayName = function() {
    console.log(this.name);
};

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

// 创建父类原型的副本并赋值给子类原型
Child.prototype = Object.create(Parent.prototype);

// 修正子类原型的 constructor 属性
Child.prototype.constructor = Child;

// 子类原型方法
Child.prototype.sayAge = function() {
    console.log(this.age);
};

// 示例使用
const child1 = new Child('Alice', 18);
child1.colors.push('black');
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
child1.sayName(); // 'Alice'
child1.sayAge(); // 18

const child2 = new Child('Bob', 20);
console.log(child2.colors); // ['red', 'blue', 'green']
child2.sayName(); // 'Bob'
child2.sayAge(); // 20

ES6 类继承(Class Inheritance)

ES6 引入了 class 关键字和 extends 关键字,使得继承变得更加简洁和直观。

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', 10);
console.log(child.name); // 输出: Child
console.log(child.age); // 输出: 10
child.sayHello(); // 输出: Hello from Parent

参考

https://www.nonhana.xyz/2024/05/06/some-research/js中如何实现类的继承/

https://segmentfault.com/a/1190000022677985

文章目录

版权

本文遵循署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0) 标准。

部分内容源自互联网、书本和课堂,仅供交流学习,未用于任何商业用途。如果它们侵犯了您的权益,请与我们联系,我们将立即删除它们。

版权所有 © 2020-2026 Kloudy 保留所有权利.