JavaScript中的继承方式详细解析

什么是继承

继承是面向对象编程中的一个重要概念,它指的是一个对象(或类)可以获得另一个对象(或类)的属性和方法。在继承中,被继承的对象通常称为父类(或基类、超类),继承属性和方法的对象称为子类(或派生类、衍生类)。

继承的主要作用包括:

  1. 代码复用:通过继承,子类可以重用父类的属性和方法,避免了重复编写相似的代码,提高了代码的可维护性和复用性。

  2. 组织代码:通过将相关的属性和方法封装在一个类中,并让其他类继承它,可以更好地组织和管理代码,使代码结构更清晰。

  3. 多态:继承是实现多态的基础。子类可以重写父类的方法,从而根据实际情况执行不同的代码逻辑,实现不同的行为。

虽然JavaScript并不是真正的面向对象语言,但是它天生的灵活性,使应用场景更加丰富。

关于继承,我们举个形象的例子:

// 父类 Animal
class Animal {constructor(name) {this.name = name;}// 父类方法speak() {console.log(`${this.name} makes a noise.`);}
}// 子类 Dog 继承自 Animal
class Dog extends Animal {constructor(name) {super(name); // 调用父类的构造函数}// 子类方法speak() {console.log(`${this.name} barks.`);}
}// 创建一个 Animal 实例
const animal = new Animal('Generic Animal');
animal.speak(); // 输出: Generic Animal makes a noise.// 创建一个 Dog 实例
const dog = new Dog('Buddy');
dog.speak(); // 输出: Buddy barks.

在这个例子中,Animal定义了一个通用的动物类它有一个构造函数和一个speak()方法,Dog类通过extends关键字继承了Animal类。它也有一个构造函数和一个speak()方法,但是speak()方法被重写,输出了不同的声音。在子类的构造函数中使用super()调用了父类的构造函数,以确保在创建子类实例时父类的属性也被正确的初始化。

继承方式

  • 原型链继承
  • 构造函数继承(借助call)
  • 组合继承
  • 原型式继承
  • 寄生虫式继承
  • 寄生组合式继承

原型链继承

原型链继承是比较常用的继承方式之一,其中涉及的构造函数、原型、实例。三者之间存在一定的关系,即每个构造函数都有一个圆形对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针

function Parent() {this.name = 'parent1';this.play = [1, 2, 3]
}
function Child() {this.type = 'child2';
}
Child.prototype = new Parent();var s1 = new Child2();
var s2 = new Child2();
s1.play.push(4);
console.log(s1.play, s2.play); // [1,2,3,4]

我们发现,在改变s1的play属性的时候,s2也跟着变化了,这是因为两个实例使用的是同一个原型对象,内存空间也是共享的。

原型链继承小结:

优点:

  • 简单易懂:实现简单,易于理解
  • 可以实现函数复用:子类可以共享父类原型中的方法

缺点:

  • 共享属性:所有的实力共享父类原型中的属性,容易造成属性修改的相互影响
  • 无法向父类传参数:无法在创建子类实例的时候向父类构造函数传递参数

 构造函数继承

构造函数继承是通过在子类构造函数中借助call || bind等调用父类的构造函数来实现继承的模式。这种方式主要是基于JavaScript中的函数特性。

举例1:

// 父类
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)和参数this.age = age;
}// 创建子类实例
var child1 = new Child('John', 10);
var child2 = new Child('Jane', 12);// 修改子类实例属性
child1.colors.push('black');console.log(child1.name); // 输出: John
console.log(child1.age); // 输出: 10
console.log(child1.colors); // 输出: ["red", "blue", "green", "black"]console.log(child2.name); // 输出: Jane
console.log(child2.age); // 输出: 12
console.log(child2.colors); // 输出: ["red", "blue", "green"]child1.sayName()//报错

可以从上面代码中发现,父类原型上定义的方法,子类是无法继承这些方法的。相比于第一种原型链继承的方法,构造函数继承的方式的父类应用不会被共享,优化了第一种继承方式的弊端。但是缺点也很明显,即不能继承原型属性或者方法。

构造函数继承小结:

优点:

  • 可以向父类传递参数:子类可以通过在构造函数中调用父类构造函数来传递参数
  • 没有共享属性:每个实例都有自己的属性,不会互相影响

缺点:

  • 无法继承方法:子类无法继承父类原型中的方法,导致方法无法复用
  • 造成内存浪费:每个实例都会单独拥有一份方法的副本,造成内存浪费
  • 无法形成真正的原型链,导致无法使用原型链上的一些方法和属性

组合继承

组合继承结合了原型链继承和构造函数继承的优点,两者结合,避免了两者的缺点。通过组合继承,我们可以实现实例属性的独立性,同时实现方法的共享,提高了代码的可维护性和复用性。

// 父类
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)和参数this.age = age;
}Child.prototype = new Parent(); // 子类的原型指向父类的实例
Child.prototype.constructor = Child; // 修正子类的构造函数为自己var s1 = new Child('hzz11', 18);
var s2 = new Child('hzz22',22);
console.log(s1)
console.log(s1.name); // hzz11
s1.colors.push('black');console.log(s1.colors); // ["red", "blue", "green", "black"]
console.log(s2.colors); // ["red", "blue", "green"]
s1.sayName(); // hzz11
s2.sayName(); // hzz22

 这种方式看上去觉得还行,似乎没什么问题的样子。但是仔细观察你会发现,上面代码中的Parent执行了两次(Parent.call(this, name)、new Parent()),造成了多构造一次的性能开销。

组合式继承小结:

优点:

  • 结合了构造函数和原型链继承的优点:通过构造函数实现实例属性,通过原型链继承共享方法,既够传递参数,又能实现方法的复用
  • 方法共享:子类实例共享父类原型中的方法,节省内存

缺点:

  • 重复的调用构造函数:在创建子类实例时,会调用两次父类构造函数,一次是在原型链继承时,一次是在构造函数继承时,可能会导致一些不必要的性能开销。

原型式继承

原型式继承是通过浅复制一个对象来创建一个新的对象,并将新对象的原型指向这个被复制的对象。这样新对象就能够继承被复制对象的属性和方法

// 原型对象
var person = {name: 'John',age: 30,sayHello: function() {console.log('Hello, my name is ' + this.name);}
};// 创建一个继承自 person 的新对象
var john = Object.create(person);console.log(john.name); // 输出: John
console.log(john.age); // 输出: 30
john.sayHello(); // 输出: Hello, my name is John

Object.create()方法说明:Object.create()的作用是创建一个新的对象,并将该对象的原型链指向另外一个对象或者null

  1. 创建新对象Object.create() 可以创建一个新的对象,该对象继承了指定的原型对象的属性和方法。

  2. 指定原型链:通过指定一个对象作为参数,新创建的对象将会继承该对象的属性和方法,并且其原型链将指向这个对象。

  3. 继承属性和方法:新创建的对象将会继承指定对象的属性和方法,包括原型链上的属性和方法。

  4. 属性描述符继承:新创建的对象会继承指定对象的属性的属性描述符(比如可枚举、可写、可配置等)。

  5. 创建原型链终点:当传入 null 作为参数时,将创建一个没有原型链的对象,这个对象将不会继承任何属性和方法,相当于创建了原型链的终点。

语法:

Object.create(proto[, propertiesObject])

其中,proto 参数是新对象的原型对象,可以是 null 或者一个对象。propertiesObject 是可选参数,用于定义额外的属性,该参数的对象属性将被添加到新创建的对象中,属性描述符则将与 Object.defineProperty() 方法的功能一致。

下面是模拟Object.create()方法的实现:

function createObject (parent) {function F () { } // 创建一个临时构造函数F.prototype = parent; // 将临时构造函数的原型指向 parent 对象return new F(); // 返回新对象
}

原型式继承小结:

优点:

  • 简单灵活:使用方便,可以快速创建对象
  • 可以通过原型链并进行属性和方法的复用

缺点:

  • 共享引用类型属性:如果一个对象的引用类型属性被修改,会影响到所有继承自同一个原型的对象

寄生式继承

寄生式继承是在原型式继承的基础上增加了对象,返回一个增强后的对象。这种模式在原有对象的基础上添加额外的方法或者属性,从而实现继承

  • 原型式继承的基础:在原型式继承中,我们通过浅复制一个对象来创建一个新对象,新对象的原型链指向被复制的对象。这样,新对象就继承了被复制对象的属性和方法。
  • 增强对象:在寄生式继承中,我们不仅创建了一个新对象,还对这个新对象进行了增强。这个增强可以包括添加新的方法、修改已有方法等。
  • 返回增强后的对象:最后,我们将这个增强后的对象返回,以供使用者使用。
// 寄生式继承函数
function createChild(parent) {var child = Object.create(parent); // 基于原型式继承创建一个对象child.sayHello = function() {      // 增加额外的方法console.log('Hello from Child');};return child;                      // 返回增强后的对象
}// 原型对象
var parent = {name: 'Parent',sayName: function() {console.log('My name is ' + this.name);}
};// 创建一个继承自 parent 的新对象
var child = createChild(parent);console.log(child.name); // 输出: Parent
child.sayName();         // 输出: My name is Parent
child.sayHello();        // 输出: Hello from Child

其优缺点也很明显,跟上面讲的原型式继承一样

寄生组合式继承

寄生组合式继承是JavaScript中一种高效的继承模式,它结合了寄生式和组合式继承的优点。寄生组合式继承的核心思想是在子类构造函数中调用父类构造函数,通过寄生式继承来继承父类的原型,以实现方法的共享和实力属性的对立性

// 父类
function Parent(name) {this.name = name;this.colors = ['red', 'blue', 'green'];
}// 父类原型方法
Parent.prototype.sayHello = function() {console.log('Hello from ' + this.name);
};// 子类
function Child(name, age) {Parent.call(this, name); // 构造函数继承,继承实例属性this.age = age;
}// 使用寄生式继承继承父类原型
function inheritPrototype(child, parent) {var prototype = Object.create(parent.prototype); // 创建父类原型的副本prototype.constructor = child; // 修正构造函数指向child.prototype = prototype; // 将子类原型指向父类原型的副本
}// 将子类原型继承父类
inheritPrototype(Child, Parent);// 创建子类实例
var child1 = new Child('John', 10);
var child2 = new Child('Jane', 12);// 修改子类实例属性
child1.colors.push('black');console.log(child1.name); // 输出: John
console.log(child1.age); // 输出: 10
console.log(child1.colors); // 输出: ["red", "blue", "green", "black"]console.log(child2.name); // 输出: Jane
console.log(child2.age); // 输出: 12
console.log(child2.colors); // 输出: ["red", "blue", "green"]child1.sayHello(); // 输出: Hello from John
child2.sayHello(); // 输出: Hello from Jane

在这个示例中,Parent 是父类构造函数,Child 是子类构造函数。通过 Parent.call(this, name) 实现了构造函数继承,继承了父类的实例属性。然后,通过 inheritPrototype(Child, Parent) 函数实现了寄生式继承,将子类原型继承父类原型的副本,从而实现了方法的共享。最后,我们创建了两个子类实例 child1child2,它们分别拥有独立的实例属性和共享的方法。

寄生组合式继承小结

优点:

  • 实现了方法的共享和实力属性的独立性,避免了构造函数继承和原型链继承的缺点
  • 避免了构造函数继承是调用两次父类构造函数的性能开销

extends继承

文章一开头,我们是使用ES6 中的extends关键字直接实现 JavaScript的继承,我们利用babel工具进行转换也可以发现,extends实际采用的也是寄生组合继承方法,因此也证明了这种方式是较优的解决继承的方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/755740.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(css)vue 自定义背景 can‘t resolve

(css)vue 自定义背景 can’t resolve 旧写法: background-image: url(/assets/images/step-bg.jpg);background-size: 100% 100%; 新写法: background-image: url(~/assets/images/step-bg.jpg);background-size: 100% 100%; 解决参考:https…

【RabbitMQ | 第七篇】RabbitMQ实现JSON、Map格式数据的发送与接收

文章目录 7.RabbitMQ实现JSON、Map格式数据的发送与接收7.1消息发送端7.1.1引入依赖7.1.2yml配置7.1.3RabbitMQConfig配置类——(非常重要)(1)创建交换器方法(2)创建队列方法(3)绑定…

代码随想录算法训练营第27天|93.复原IP地址、78.子集、90.子集二

目录 一、力扣93.复原IP地址1.1 题目1.2 思路1.3 代码1.4 总结 二、力扣78.子集2.1 题目2.2 思路2.3 代码2.4 总结 三、力扣90.子集二3.1 题目3.2 思路3.3 代码3.4 总结 一、力扣93.复原IP地址 (比较困难,做起来很吃力) 1.1 题目 1.2 思路 …

【数据结构练习题】栈——1.括号匹配 2.逆波兰表达式求值 3.出栈入栈次序匹配 4.最小栈

♥♥♥♥♥个人主页♥♥♥♥♥ ♥♥♥♥♥数据结构练习题总结专栏♥♥♥♥♥ 文件目录 前言1.括号匹配1.1问题描述1.2解题思路1.3画图解释1.4代码实现2.逆波兰表达式求值 2.1问题描述2.2解题思路2.3画图解释2.4代码解释3.出栈入栈次序匹配 3.1问题描述3.2思路分析3.3画图解释3.…

【No.13】蓝桥杯二分查找|整数二分|实数二分|跳石头|M次方根|分巧克力(C++)

二分查找算法 知识点 二分查找原理讲解在单调递增序列 a 中查找 x 或 x 的后继在单调递增序列 a 中查找 x 或 x 的前驱 二分查找算法讲解 枚举查找即顺序查找, 实现原理是逐个比较数组 a[0:n-1] 中的元素,直到找到元素 x 或搜索整个数组后确定 x 不在…

CPU设计实战—异常处理指令

异常类型以及精确异常的处理 异常有点像中断,处理完还要回到原来的状态,所以需要对之前的状态进行保存。本CPU主要实现对以下异常的处理: 1.外部硬件中断 2.复位异常 3.系统调用异常(发生在译码阶段) 4.溢出异常&…

做好外贸网站SEO优化,拓展海外市场

随着全球贸易的发展和互联网的普及,越来越多的外贸企业将目光投向了网络,希望通过建立网站来拓展海外市场。然而,在竞争激烈的外贸市场中,要让自己的网站脱颖而出,吸引更多的目标客户,就需要进行有效的SEO优…

openGauss学习笔记-246 openGauss性能调优-SQL调优-经验总结:SQL语句改写规则

文章目录 openGauss学习笔记-246 openGauss性能调优-SQL调优-经验总结:SQL语句改写规则246.1 使用union all代替union246.2 join列增加非空过滤条件246.3 not in转not exists246.4 选择hashagg246.5 尝试将函数替换为case语句246.6 避免对索引使用函数或表达式运算2…

面试算法-50-二叉树的最大深度

题目 给定一个二叉树 root ,返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1: 输入:root [3,9,20,null,null,15,7] 输出:3 解 class Solution {public int maxDepth(TreeNo…

《算法设计与分析第二版》100行 C语言实现 广度度优先算法 BFS——最短距离

抄录自课本P157页。 #include <stdio.h> #define MAXQ 100 // 队列大小 #define MAxN 10 // 最大迷宫大小 int n8; // 迷宫大小 char Maze [MAxN][MAxN] {{O,X,X,X,X,X,X,X,},{O,O,O,X,O,X,O,X,},{X,X,O,O,O,X,O,X,},{X,X,O,X,O,X,X,X,},…

HDFS概述及常用shell操作

HDFS 一、HDFS概述1.1 HDFS适用场景1.2 HDFS优缺点1.3 HDFS文件块大小 二、HDFS的shell操作2.1 上传2.2 下载2.3 HDFS直接操作 一、HDFS概述 1.1 HDFS适用场景 因为HDFS里所有的文件都是维护在磁盘里的 在磁盘中对文件的历史内容进行修改 效率极其低(但是追加可以) 1.2 HDF…

走近 AI Infra 架构师:在高速飞驰的大模型“赛车”上“换轮子”的人

如果把大模型训练比作 F1 比赛&#xff0c;长凡所在的团队就是造车的人&#xff0c;也是在比赛现场给赛车换轮子的人。1% 的训练提速&#xff0c;或者几秒之差的故障恢复时间&#xff0c;累积起来&#xff0c;都能影响到几百万的成本。长凡说&#xff1a;“大模型起来的时候&am…

算法详解——选择排序和冒泡排序

一、选择排序 选择排序算法的执行过程是这样的&#xff1a;首先&#xff0c;算法遍历整个列表以确定最小的元素&#xff0c;接着&#xff0c;这个最小的元素被置换到列表的开头&#xff0c;确保它被放置在其应有的有序位置上。接下来&#xff0c;从列表的第二个元素开始&#x…

事件高级、

文章目录 1.注册事件&#xff08;绑定事件&#xff09;addEventListener 事件监听方式attachEvent 事件监听方式、兼容性解决方案 * 2.删除事件&#xff08;解绑事件&#xff09;删除事件的方式删除事件兼容性解决方案 * 3.DOM事件流4.事件对象使用语法兼容性方案*常见属性和方…

边缘计算+WEB端应用融合:AI行为识别智能监控系统搭建指南 -- 边缘设备图像识别及部署(二)

专栏目录 边缘计算WEB端应用融合&#xff1a;AI行为识别智能监控系统搭建指南 – 整体介绍&#xff08;一&#xff09; 边缘计算WEB端应用融合&#xff1a;AI行为识别智能监控系统搭建指南 -- 边缘图像识别及部署&#xff08;二&#xff09; 前言边缘图像识别与推流整体思路原始…

海康威视添加新摄像头到原建的网络监控平台中

一、适用场景 1、企业已经存在一套海康威视的监控网络系统&#xff1b; 2、根据业务的需求&#xff0c;要新增加一些摄像头&#xff1b; 3、原施工方忙碌&#xff0c;为新增加的摄像头施工成本较高&#xff1b; 4、新增加海康威视的摄像头视频监控&#xff0c;保存在原建的监控…

构建强大的API:Django中的REST框架探究与实践【第146篇—Django】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 构建强大的API&#xff1a;Django中的REST框架探究与实践 在当今的Web开发中&#xff0c;构…

Apache SeaTunnel MongoDB CDC 使用指南

随着数据驱动决策的重要性日益凸显&#xff0c;实时数据处理成为企业竞争力的关键。SeaTunnel MongoDB CDC(Change Data Capture) 源连接器的推出&#xff0c;为开发者提供了一个高效、灵活的工具&#xff0c;以实现对 MongoDB 数据库变更的实时捕获和处理。 本文将深入探讨该连…

Jackson 2.x 系列【1】概述

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Jackson 版本 2.17.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-seata-demo 文章目录 1. 前言2. 什么是 JSON3. 常用 Java JSON 库4. Jackson4.1 简介4.2 套件4.3 模块4.…

prompt开发生命周期

1.定义任务场景和成功标准 任务场景可分为简单任务&#xff1a;实体抽取、qa等 复杂任务&#xff1a;代码生成、创意写作等 在定义任务后&#xff0c;就要定义模型实现该任务的成功标准&#xff1a; 模型表现和准确率&#xff1b;延迟&#xff1b;价格。 2.开发测试用例 多…