JavaScript继承的多种实现方式详解

前言

在JavaScript中,继承是面向对象编程的重要概念之一。通过继承,我们可以让一个对象获得另一个对象的属性和方法,从而实现代码复用。在《JavaScript高级程序设计(第三版)》第6.3节中,详细介绍了JavaScript中实现继承的多种方式。

本文将深入探讨这些继承方式,包括它们的实现原理、优缺点以及适用场景。为了让读者更好地理解,我们还将通过图解来直观展示各种继承方式的工作原理。

1. 原型链继承

原型链继承是JavaScript中最基本的继承方式,它利用了原型链的特性来实现继承。

1.1 实现原理

原型链继承的核心思想是让一个构造函数的原型对象等于另一个类型的实例。这样,子类型就可以访问到超类型的所有属性和方法。

javascript 复制代码
function SuperType() {
    this.property = true;
}

SuperType.prototype.getSuperValue = function() {
    return this.property;
};

function SubType() {
    this.subproperty = false;
}

// 实现继承
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
    return this.subproperty;
};

var instance = new SubType();
console.log(instance.getSuperValue()); // true

1.2 优缺点分析

优点:

  • 实现简单,易于理解
  • 能够复用超类型的原型方法

缺点:

  • 包含引用类型值的原型属性会被所有实例共享
  • 创建子类型实例时,不能向超类型的构造函数传递参数

1.3 工作原理解析

2. 借用构造函数继承

借用构造函数继承也被称为伪造对象或经典继承,它通过在子类型构造函数的内部调用超类型构造函数来实现继承。

2.1 实现原理

javascript 复制代码
function SuperType(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

function SubType(name, age) {
    // 继承SuperType并传递参数
    SuperType.call(this, name);
    
    // 实例属性
    this.age = age;
}

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); // ["red", "blue", "green", "black"]

var instance2 = new SubType("Greg", 27);
console.log(instance2.colors); // ["red", "blue", "green"]

2.2 优缺点分析

优点:

  • 避免了引用类型被共享的问题
  • 可以向超类型构造函数传递参数

缺点:

  • 方法都在构造函数中定义,无法复用函数
  • 不能访问到超类型的原型中定义的方法

2.3 工作原理解析

3. 组合继承

组合继承也叫伪经典继承,它结合了原型链继承和借用构造函数继承的优点。

3.1 实现原理

javascript 复制代码
function SuperType(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

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

function SubType(name, age) {
    // 继承属性
    SuperType.call(this, name);
    
    this.age = age;
}

// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;

SubType.prototype.sayAge = function() {
    console.log(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); // ["red", "blue", "green", "black"]
instance1.sayName(); // "Nicholas"
instance1.sayAge(); // 29

var instance2 = new SubType("Greg", 27);
console.log(instance2.colors); // ["red", "blue", "green"]
instance2.sayName(); // "Greg"
instance2.sayAge(); // 27

3.2 优缺点分析

优点:

  • 避免了引用类型被共享的问题
  • 可以向超类型构造函数传递参数
  • 可以复用超类型原型中定义的方法
  • instanceof和isPrototypeOf()能够正确识别基于构造函数的关系

缺点:

  • 无论什么情况下,都会调用两次超类型构造函数

3.3 工作原理解析

4. 原型式继承

原型式继承借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

4.1 实现原理

javascript 复制代码
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]

4.2 优缺点分析

优点:

  • 不必创建自定义类型即可实现继承
  • 实现简单

缺点:

  • 包含引用类型值的属性始终都会共享相应的值

4.3 工作原理解析

5. 寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路,它创建一个仅用于封装继承过程的函数。

5.1 实现原理

javascript 复制代码
function createAnother(original) {
    var clone = object(original); // 通过调用函数创建一个新对象
    clone.sayHi = function() {    // 以某种方式来增强这个对象
        console.log("hi");
    };
    return clone;                 // 返回这个对象
}

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // "hi"

5.2 优缺点分析

优点:

  • 可以在不影响原始对象的情况下,为新对象添加方法

缺点:

  • 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率

5.3 工作原理解析

6. 寄生组合式继承

寄生组合式继承是引用类型最理想的继承范式,它结合了组合继承和寄生式继承的优点。

6.1 实现原理

javascript 复制代码
function inheritPrototype(subType, superType) {
    var prototype = object(superType.prototype); // 创建对象
    prototype.constructor = subType;             // 增强对象
    subType.prototype = prototype;               // 指定对象
}

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

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

function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function() {
    console.log(this.age);
};

6.2 优缺点分析

优点:

  • 只调用了一次SuperType构造函数
  • 避免了在SubType.prototype上面创建不必要的、多余的属性
  • 原型链还能保持不变

缺点:

  • 实现相对复杂

6.3 工作原理解析

总结

JavaScript中的继承方式多种多样,每种方式都有其适用的场景:

  1. 原型链继承:适用于简单场景,但要注意引用类型值共享的问题
  2. 借用构造函数继承:适用于需要避免引用类型值共享的场景
  3. 组合继承:JavaScript中最常用的继承模式,结合了前两种方式的优点
  4. 原型式继承:适用于基于已有对象创建新对象的场景
  5. 寄生式继承:适用于在不影响原始对象的情况下,为新对象添加方法
  6. 寄生组合式继承:是引用类型最理想的继承范式

在实际开发中,我们应该根据具体需求选择合适的继承方式。随着ES6的普及,class语法糖也成为了实现继承的一种新方式,但理解这些底层的继承原理对于深入掌握JavaScript仍然非常重要。

最后,创作不易请允许我插播一则自己开发的"数规规-排五助手"(有各种预测分析)小程序广告,感兴趣可以微信小程序体验放松放松,程序员也要有点娱乐生活,搞不好就中个排列五了呢?

感兴趣可以搜索微信小程序"数规规排五助手"体验体验!!

如果觉得本文有用,欢迎点个赞👍+收藏⭐+关注支持我吧!

相关推荐
ybb_ymm3 小时前
前端开发之ps基本使用
前端·css
Ashley的成长之路3 小时前
NativeScript-Vue 开发指南:直接使用 Vue构建原生移动应用
前端·javascript·vue.js
衿璃3 小时前
Flutter应用架构设计的思考
前端·flutter
朕的剑还未配妥3 小时前
Vue 2 响应式系统常见问题与解决方案(包含_demo以下划线开头命名的变量导致响应式丢失问题)
前端·vue.js
JNU freshman4 小时前
Element Plus组件
前端·vue.js·vue3
一只专注api接口开发的技术猿4 小时前
容器化与调度:使用 Docker 与 K8s 管理分布式淘宝商品数据采集任务
开发语言·前端·数据库
我有一棵树4 小时前
性能优化之前端与服务端中的 Gzip 压缩全解析
前端
魔术师卡颂4 小时前
不就写提示词?提示词工程为啥是工程?
前端·人工智能·后端
訾博ZiBo4 小时前
【Vibe Coding】001-前端界面常用布局
前端