返回

原生 JS 继承模型完全解析:揭开对象间关系的神秘面纱

前端

前言

继承是面向对象编程(OOP)中的一种重要概念,它允许对象从另一个对象或类中获取属性和行为。在原生 JavaScript 中,没有内置的继承机制,但我们可以通过各种技巧来实现对象之间的继承关系。本文将详细介绍原生 JavaScript 中的各种继承模型,帮助您理解和掌握对象间关系的构建和应用。

经典继承

经典继承是 JavaScript 中最基本和最简单的继承模型。它通过直接赋值来实现继承关系,即将父对象的所有属性和方法直接复制到子对象中。经典继承的语法非常简单,如下所示:

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

function Child() {
  // 直接赋值实现继承
  this.age = 20;
}

// 继承父对象
Child.prototype = new Parent();

在上面的例子中,Parent 是父类,Child 是子类。通过将 Parent.prototype 赋值给 Child.prototype,子类 Child 就继承了父类 Parent 的所有属性和方法。

构造函数继承

构造函数继承是另一种常用的继承模型,它通过调用父类的构造函数来实现继承关系。构造函数继承的语法如下所示:

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

function Child(name, age) {
  // 调用父类的构造函数实现继承
  Parent.call(this, name);

  this.age = age;
}

在上面的例子中,Parent 是父类,Child 是子类。通过在 Child 的构造函数中调用 Parent.call(this, name),子类 Child 就继承了父类 Parent 的所有属性和方法。

组合继承

组合继承是经典继承和构造函数继承的组合,它同时使用了两种继承模型的优点。组合继承的语法如下所示:

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

function Child(name, age) {
  // 调用父类的构造函数实现继承
  Parent.call(this, name);

  // 直接赋值实现继承
  this.age = age;
}

// 继承父对象
Child.prototype = Object.create(Parent.prototype);

在上面的例子中,Parent 是父类,Child 是子类。通过在 Child 的构造函数中调用 Parent.call(this, name),子类 Child 就继承了父类 Parent 的所有属性和方法。然后,通过 Object.create(Parent.prototype) 创建一个新的对象,并将它赋值给 Child.prototype,这样子类 Child 就继承了父类 Parent 的所有属性和方法。

寄生组合继承

寄生组合继承是组合继承的改进版本,它通过在子类中创建一个新的对象来实现继承关系,从而避免了在子类中直接修改父类原型对象的问题。寄生组合继承的语法如下所示:

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

function Child(name, age) {
  // 创建一个新的对象实现继承
  var obj = Object.create(Parent.prototype);

  // 调用父类的构造函数实现继承
  Parent.call(obj, name);

  // 直接赋值实现继承
  obj.age = age;

  // 返回新的对象
  return obj;
}

在上面的例子中,Parent 是父类,Child 是子类。通过在 Child 的构造函数中创建一个新的对象 obj,并调用 Parent.call(obj, name),子类 Child 就继承了父类 Parent 的所有属性和方法。然后,通过 obj.age = age,子类 Child 又获得了自己的属性 age。最后,将 obj 返回,作为子类 Child 的实例。

代理继承

代理继承是通过创建一个代理对象来实现继承关系,代理对象将父对象的所有属性和方法委托给子对象。代理继承的语法如下所示:

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

function Child(name, age) {
  // 创建代理对象实现继承
  var proxy = new Proxy({}, {
    get: function(target, property) {
      // 将属性委托给父对象
      return Parent.prototype[property];
    }
  });

  // 直接赋值实现继承
  proxy.age = age;

  // 返回代理对象
  return proxy;
}

在上面的例子中,Parent 是父类,Child 是子类。通过在 Child 的构造函数中创建一个代理对象 proxy,并通过 Parent.prototype[property] 将属性委托给父对象,子类 Child 就继承了父类 Parent 的所有属性和方法。然后,通过 proxy.age = age,子类 Child 又获得了自己的属性 age。最后,将 proxy 返回,作为子类 Child 的实例。

函数继承

函数继承是一种简单的继承模型,它通过将子对象作为父对象的一个函数来实现继承关系。函数继承的语法如下所示:

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

function Child(name, age) {
  // 将子对象作为父对象的一个函数实现继承
  Parent.call(this, name);

  this.age = age;
}

// 设置子对象的原型对象为父对象
Child.prototype = new Parent();

在上面的例子中,Parent 是父类,Child 是子类。通过将 Child 作为 Parent 的一个函数来调用,子类 Child 就继承了父类 Parent 的所有属性和方法。然后,通过 Child.prototype = new Parent(),将子对象的原型对象设置为父对象,这样子类 Child 就完全继承了父类 Parent 的所有属性和方法。

借用构造函数

借用构造函数是一种简单而直接的继承模型,它通过在子类的构造函数中调用父类的构造函数来实现继承关系。借用构造函数的语法如下所示:

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

function Child(name, age) {
  // 在子类的构造函数中调用父类的构造函数实现继承
  Parent.call(this, name);

  this.age = age;
}

在上面的例子中,Parent 是父类,Child 是子类。通过在 Child 的构造函数中调用 Parent.call(this, name),子类 Child 就继承了父类 Parent 的所有属性和方法。然后,通过 this.age = age,子类 Child 又获得了自己的属性 age

总结

本文详细介绍了原生 JavaScript 中的各种继承模型,包括经典继承、构造函数继承、组合继承、寄生组合继承、代理继承、函数继承和借用构造函数等。这些继承模型各有其特点和优缺点,开发者可以根据实际情况选择合适的继承模型来构建对象间的继承关系。