深入探讨 JavaScript 中的继承:实现继承的五种经典方式(附图解)

前言

  • 在 JavaScript 中,实现继承的几种常见方式包括:
    *

    1. 原型链继承: 此种方式也有两种方式
    • 1.1 共享同一个父类实例
    • 1.2 绕过父类实例,共享同一个类型原型
      1. 构造函数继承(借用构造函数)
      1. 组合继承
      1. 原型式继承
      1. ES6 类继承

原型链继承

原型链继承-共享同一个父类实例

这是 JavaScript 最早的继承方式,通过将子类的原型对象指向父类的实例,实现子类继承父类的属性和方法。但它有一个缺点,就是所有子类实例共享同一个父类实例。

js 复制代码
function Parent() {
  this.property = "parentProperty";
}

Parent.prototype.say = function () {
  console.log("Parent say");
};

function Child() {
  this.childProperty = "childProperty";
}

// 修改 Child 的 prototype 属性指向 Parent 实例对象,那么 Child 实例对象的 __proto__ 就会指向其构造函数 Child 的 prototype 属性(即Parent 实例对象)
// 修改了 Child.prototype 的指向后,那么原来 Child.prototype 指向的对象由于被没有引用,就会被回收。
Child.prototype = new Parent();

const childInstance = new Child();
console.log(childInstance.property); // 输出 'parentProperty'
childInstance.say();

下图是代码图解:

上面的代码看似没有问题,但其实还是存在缺陷:

js 复制代码
console.log(Child.prototype.constructor); // 输出:[Function: Parent], 即是 Parent 构造函数,这是由于 Child.prototype 自身没有,就会沿着 __proto__ 寻找,因此找到 Parent。这明显是不对的

console.log(Child.prototype.constructor === Child); // 输出:false, 这明显也是不对的

因此我们需要再修改 Child.prototype 的指向之后(即代码 Child.prototype = new Parent();),需要同时修改 Child.prototype.constructor 的指向:

diff 复制代码
Child.prototype = new Parent();
+Child.prototype.constructor = Child;
  • 后续代码也是同理!

原型链继承-绕过父类实例,共享同一个父类的原型

js 复制代码
function Parent() {
  this.property = "parentProperty";
}

Parent.prototype.say = function () {
  console.log("Parent say");
};

function Child() {
  Parent.call(this);
  this.childProperty = "childProperty";
}

// 方式一:直接指向
// Child.prototype.__proto__ = Parent.prototype;

// 方式二:使用 Object.create(),这是 es5 的方法
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

let childInstance = new Child();
console.log(childInstance.property);
childInstance.say();

下图是代码图解:

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

这种方式通过在子类构造函数中调用父类构造函数,实现继承属性。这样每个子类实例都拥有独立的属性副本,但无法继承父类原型上的方法。

js 复制代码
function Parent() {
  this.property = "parentProperty";
}

Parent.prototype.say = function () {
  console.log("Parent say");
};

function Child() {
  Parent.call(this);
  this.childProperty = "childProperty";
}

const childInstance = new Child();
console.log(childInstance.property); // 输出 'parentProperty'
// childInstance.say() // 报错:childInstance.say is not a function

下图是代码图解:

组合继承

组合继承结合了原型链继承和构造函数继承,通过在子类构造函数中调用父类构造函数,然后设置子类的原型为一个父类实例,实现了既能继承属性又能继承方法。

js 复制代码
function Parent() {
  this.property = "parentProperty";
}

Parent.prototype.say = function () {
  console.log("Parent say");
};

function Child() {
  Parent.call(this);
  this.childProperty = "childProperty";
}

Child.prototype = new Parent();
// 注意:修改其原型对象之后,同时必须得修改 constructor 的指向
Child.prototype.constructor = Child;

const childInstance = new Child();
console.log(childInstance.property); // 输出 'parentProperty'
childInstance.say();

原型式继承

这种继承方式创建一个临时的构造函数,将这个构造函数的原型指向父构造函数的原型,再将子构造函数的原型指向该构造函数的实例,从而实现继承。

js 复制代码
function mockExtend(Parent, Child) {
  function Fn() {}

  /**
   * 1. 修改了 Fn.prototype 的指向后,那么原来的 Fn.prototype 没有被引用,则会被回收
   * 2. 那么Fn的实例对象的 __proto__ 就指向其构造函数的 prototype
   */
  Fn.prototype = Parent.prototype;

  Child.prototype = new Fn();
  // 注意:修改了原型对象之后,同时必须得修改 constructor 的指向
  Child.prototype.constructor = Child;
}

// =============================== 使用 ==================================
function Parent() {
  this.property = "parentProperty";
}

Parent.prototype.say = function () {
  console.log("Parent say");
};

function Child() {
  Parent.call(this);
  this.childProperty = "childProperty";
}

mockExtend(Parent, Child);

const childInstance = new Child();
console.log(childInstance.property);
childInstance.say();

下图是代码图解:

ES6 类继承

ES6 引入了 class 关键字,使继承更加易读和易用。通过 extends 关键字,一个类可以继承另一个类的属性和方法。

js 复制代码
class Parent {
  constructor() {
    this.property = "parentProperty";
  }

  say() {
    console.log("Parent say");
  }
}

class Child extends Parent {
  constructor() {
    super();
    this.childProperty = "childProperty";
  }
}

const childInstance = new Child();
console.log(childInstance.property); // 输出 'parentProperty'
childInstance.say();
相关推荐
每天吃饭的羊4 分钟前
水平,垂直居中
前端·javascript·html
鼎道开发者联盟26 分钟前
鼎享会 | OpenClaw Control UI 前端架构全解析:自研 UI 对接 Server 实操指南
前端·ui·架构·openclaw·control ui
尘世中一位迷途小书童28 分钟前
一套完整的给予ceium封装的组件库,可满足企业级开发
前端
Z_Wonderful29 分钟前
微前端:Webpack 配置 vs Vite 配置 超清晰对比
前端·webpack·node.js
thankseveryday31 分钟前
Three.js 把 Blender 绘制的曲线(Bezier / 曲线) 导入 Three.js 并作为运动路径 / 动画路径使用
开发语言·javascript·blender
码云数智-园园35 分钟前
HTTPS是如何工作的?从HTTP到HTTPS的加密演进
前端
隔窗听雨眠43 分钟前
HTML头部元信息避坑指南
前端·html
Gauss松鼠会1 小时前
【openGauss】openGauss 磁盘引擎之 ustore
java·服务器·开发语言·前端·数据库·经验分享·gaussdb
LIO1 小时前
前端响应式页面开发全攻略:核心技术 + 实现效果 + 实战指南
前端·响应式设计
得物技术1 小时前
AI驱动:从运营行为到自动化用例的智能化实践|得物技术
前端·ai编程·全栈