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仍然非常重要。

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

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

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

相关推荐
不一样的少年_15 小时前
【前端效率工具】再也不用 APIfox 联调!零侵入 Mock,全程不改代码、不开代理
前端·javascript·浏览器
IT_陈寒15 小时前
JavaScript 性能优化实战:我通过这7个技巧将页面加载速度提升了65%
前端·人工智能·后端
JIngJaneIL15 小时前
数码商城系统|电子|基于SprinBoot+vue的商城推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·毕设·数码商城系统
GISer_Jing15 小时前
Flutter架构解析:从引擎层到应用层
前端·flutter·架构
GISer_Jing15 小时前
Flutter开发全攻略:从入门到精通
android·前端·flutter
艾小码15 小时前
Vue组件通信不再难!这8种方式让你彻底搞懂父子兄弟传值
前端·javascript·vue.js
lcc18715 小时前
Vue 数据代理
前端·javascript·vue.js
Moment15 小时前
为什么我们从 Python 迁移到 Node.js
前端·后端·node.js
excel15 小时前
📘 全面解析:JavaScript 时间格式化 API 实战指南
前端
咖啡の猫16 小时前
Vue基本路由
前端·vue.js·状态模式