JavaScript如何实现继承?

news2024/11/25 18:36:03

5d9c4450-72a3-11eb-85f6-6fac77c0c9b3

📝个人主页:爱吃炫迈
💌系列专栏:JavaScript
🧑‍💻座右铭:道阻且长,行则将至💗

文章目录

  • 继承
  • JavaScript如何实现继承?
    • 原型链继承
    • 构造函数继承
    • 组合继承
    • 原型式继承
    • 寄生式继承
    • 寄生组合式继承
    • ES6类继承-extends
  • 总结


继承

🍇继承是什么?

继承(inheritance)是面向对象软件技术当中的一个概念。
如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类”也可以称“A是B的超类”

🍇关于继承,我们举个例子理解一下

  • 几何形状只有两种,即椭圆形和多边形。
  • 圆是椭圆形的一种,它只有一个焦点。
  • 三角形、矩形和五边形都是多边形的一种,具有不同数量的边。
  • 这就构成了一种完美的继承关系。

Snipaste_2023-04-20_22-10-18

在这个例子中:

  • 形状椭圆形多边形基类(所有类都由它继承而来)。
  • 圆形继承了椭圆形,因此圆形是椭圆形的子类,椭圆形是圆形的超类
  • 三角形、矩形和五边形都继承了多边形,因此它们是多边形的子类,多边形是它们的超类
  • 正方形继承了矩形,因此正方形是矩形的子类,矩形是正方形的超类

🍇继承的优点

  • 继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码

  • 在子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能

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


JavaScript如何实现继承?

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

原型链继承

🥝学习原型链继承的前提是我们要知道JavaScript原型链的机制

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

Snipaste_2023-04-20_22-33-05

🥝举个例子实现原型链继承

// 1.定义父类构造函数
function Person() {
  this.name = "jack";
}
// 2.父类原型上添加内容
Person.prototype.running = function () {
  console.log(this.name + "running");
};
// 3.定义子类构造函数
function Student() {
  this.sno = 111;
}
// 4.创建父类对象,并且作为子类的原型对象
let p = new Person();
Student.prototype = p;
// 5.在子类原型上添加内容
Student.prototype.studying = function () {
  console.log(this.name + "studying");
};
// 6.创建子类对象
let stu = new Student();

// 测试
console.log(stu.name);  //jack
stu.running();  //jack running

🥝继承创建对象的内存图

Snipaste_2023-04-20_23-12-09

  • 第4步let p = new Person();Student.prototype = p;使Student类的原型指向p对象。
  • 第5步创建子类对象stu对象let stu = new Student();使stu的隐式原型(_proto_)指向Student的显式原型对象,即p对象
  • 测试stu.namestu.running中的name属性和running属性在stu对象中找不到,就沿着原型链寻找,最终在p对象中找到。

🥝原型链继承的弊端

  • 继承的属性其实是保存在p对象上的

  • 我们通过直接打印对象是看不到这个属性的

    console.log(stu)

Snipaste_2023-04-20_23-32-02

  • 这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题
function Person() {
  this.name = "jack";
  this.friends = []
}
let stu = new Student();
let stu1 = new Student();

// *获取引用,修改引用中的值,会相互影响
stu1.friends.push("kobe") 

// *补充:直接修改对象上的属性,是给对象添加了一个新属性
stu.name = "kobe";
console.log(stu1.name); //jack

console.log(stu.friends);  //kobe
console.log(stu1.friends);  //kobe
  • 不能给Person传递参数(让每个stu有自己的属性),因为这个对象是一次性创建的(没办法定制化)

构造函数继承

  • 在子类型构造函数的内部调用父类型构造函数。
  • 因为函数可以在任意的时刻被调用,
  • 因此通过apply()call()方法也可以在新创建的对象上执行构造函数;

🍌举例实现构造函数继承

function Person(name, age, friends) {
    this.name = name;
    this.age = age;
    this.friends = friends
}

function Student(name, age, friends, sno) {
   // 继承自Person
    Person.call(this, name, age, friends);
    this.sno = sno;
}

let stu1 = new Student("why", 18, ["lilei"], 111);
stu1.friends.push("lucy");
console.log(stu1.friends); //['lilei', 'lucy']

let stu2 = new Student("kobe", 18, ["james"], 112);
console.log(stu2.friends); //['james']

核心代码是Person.call(this),创建子类实例时调用Person构造函数,于是Student的每个实例都会将Person中的属性复制一份。

构造函数优点:解决了原型链继承的缺点

🍌构造函数继承缺点

  • 只能继承父类的实例属性和方法,不能继承原型属性/方法
  • 无法实现复用,每个子类都有父类实例函数的副本,影响性能

组合继承

组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。

🍉举例实现组合继承

function Person(name, age, friends) {
    this.name = name;
    this.age = age;
    this.friends = friends
}

function Student(name, age, friends, sno) {
   // 继承属性
   // 第二次调用Person()
    Person.call(this, name, age, friends);
    this.sno = sno;
}

// 继承方法
// 构建原型链
// 第一次调用Person()
let p = new Person();
Student.prototype = p;

let stu1 = new Student("why", 18, ["lilei"], 111);
let stu2 = new Student("kobe", 18, ["james"], 112);

console.log(stu1); //Student {name: 'why', age: 18, friends: Array(1), sno: 111}

stu1.friends.push("lucy");

console.log(stu1); //Student {name: 'why', age: 18, friends: Array(2), sno: 111}
console.log(stu1.friends); //['lilei', 'lucy']
console.log(stu2.friends); //['james']

🍉组合继承内存图

Snipaste_2023-04-21_00-26-30

🍉组合继承缺点

  • 第一次调用Person.prototype写入三个属性name,friends,age。
  • 第二次调用Person():给stu1写入两个属性name,friends,age。

🍉实例对象stu1上的两个属性就屏蔽了其原型对象Person.prototype的两个同名属性。所以,组合模式的缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。


原型式继承

🥑利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。

var person = {
    name: "jack",
    friends: ["kobe", "lucy"],
};

🥑object()对传入的对象执行了一次浅拷贝,将构造函数F的原型直接指向传入的对象

function object(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
    // 等价于
    // var newObj = new F();
    // return newObj;
    // 即newObj._proto_=F.prototype=obj
}

var student1 = object(person);
console.log(student1.name);  //jack
student1.friends.push("Bob");

🥑*Object.create()方法实现原型式继承,Object.create()可替代上面的object方法*

var student2 = Object.create(person);
student2.name = "Linda";
console.log(student2.name);  //Linda
student2.friends.push("Greg");

🥑测试

console.log(person.friends); //['kobe', 'lucy', 'Bob', 'Greg']

🥑原型式继承缺点

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
  • 无法传递参数

寄生式继承

思路:寄生式继承的思路是结合原型类继承和工厂模式的一种方式
核心:创建一个封装继承过程的函数, 该函数在内部以某种方式来增强对象,最后再将这个对象返回

🌽举例实现寄生式继承

var person = {
    name: "jack",
    friends: ["kobe", "lucy"],
};

function createStudent(person) {
    var newObj = Object.create(person);
    newObj.studying = function () {
        console.log(this.name + " studying");
    };
    return newObj;
}

var student1 = createStudent(person);
student1.friends.push("Bob");

console.log(person.friends); //['kobe', 'lucy', 'Bob']
console.log(student1.friends); //['kobe', 'lucy', 'Bob']
console.log(student1.name); //jack
student1.studying(); //jack studying

🌽寄生式继承缺点(和原型式继承一样)

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
  • 无法传递参数

寄生组合式继承

借助解决普通对象的继承问题的 Object.create 方法,在全面几种继承方式的优缺点基础上进行改造,这也是所有继承方式里面相对最优的继承方式。

🥕举例实现寄生组合式继承

function inheritPrototype(subType, superType) {
    var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本
    prototype.constructor = subType; // 增强对象,弥补因重写原型而失去的默认的constructor 属性
    subType.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型
}

// 父类初始化实例属性和原型属性
function person(name) {
    this.name = name;
    this.colors = ["red"];
}
person.prototype.sayName = function () {
    console.log(this.name);
};

// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function student(name, age) {
    person.call(this, name);
    this.age = age;
}

// 将父类原型指向子类
inheritPrototype(student, person);

// 新增子类原型属性
student.prototype.sayAge = function () {
    console.log(this.age);
};

var student1 = new student("jack", 23);
var student2 = new student("kobe", 25);

student1.colors.push("blue");
student1.colors.push("green");

console.log(student1.colors); //['red', 'blue', 'green']
console.log(student2.colors); //['red']
student1.sayName(); //jack
student2.sayAge(); //25

🎉🎉综上,我们花了很大的篇幅讨论了在ES5中实现继承的方案,虽然最终实现了相对满意的继承机制,但是过程却依然是非常繁琐的。

在ES6中新增了使用extends关键字,可以方便的帮助我们实现继承🎉🎉🎉


ES6类继承-extends

  • extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。
  • 其中constructor表示构造函数,一个类只能有一个构造函数,有多个会报出SyntaxError错误。
  • 如果没有显式指定构造方法,则会添加默认的 constructor方法。
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    running() {
        console.log(this.name + " running");
    }
    eating() {
        console.log("eating1");
        console.log("eating2");
    }
    static personMethod() {
        console.log("personMethod");
    }
}
class Student extends Person {
    constructor(name, age, sno) {
        // 1.super用法1
        super(name, age);
        this.sno = sno;
    }
    // 子类对父类方法的重写
    running() {
        console.log(stu + " running");
    }

    eating() {
        // 2.super用法2:复用父类中的处理逻辑
        super.eating();
        console.log("eating3");
        console.log("eating4");
    }

    static studentMethod() {
        // 3.super用法3
        super.personMethod();
        console.log("studentMethod");
    }
}

var stu = new Student("kobe", 18, 111);
stu.running();
// 1.若没有重写,则输出:kobe running
// 2.若重写了,则优先找自己的方法,则输出:stu running
stu.eating(); //eating1 eating2 eating3 eating4
Student.studentMethod(); //personMethod studentMethod
console.log(stu); //{name: 'kobe', age: 18, sno: 111}

🥕我们会发现在上面的代码中我使用了一个super关键字,这个super关键字有不同的使用方式:

  • 注意:在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数!
  • super的使用位置有三个:子类的构造函数、实例方法、静态方法;

总结

以一张图总结:
请添加图片描述
通过Object.create 来划分不同的继承方式,最后的寄生式组合继承方式是通过组合继承改造之后的最优继承方式,而 extends 的语法糖和寄生组合继承的方式基本类似。

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

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

相关文章

纽扣电池出口欧盟ce认证EN62133测试项目

纽扣电池CE证办理,锂电CE证旨在提高环境性能的2006/66/EC入了电池和 蓄电池中0.0005%汞和便携式电池和蓄电池中0.002%镉的限值。自2013/56/EU 修订了2006/66/EC,2013/56/EU(修订2006/66/)规定,2015年10月1日 起,纽扣电池中汞的…

从零基础到条码高手:傻瓜式操作,告别excel、AI和PS的烦恼

条形码是一种用于商品识别、库存管理等方面的编码标识系统,它是通过将数字和字符以特定的图案排列组合起来,从而形成一组能被机器扫描和识别的条纹图案。 通常情况下,条形码的生成可以分为如下几个步骤: 1、编号:首先…

【神秘题 整数溢出】牛客小白月赛71 C-猫猫与数列

被教育了 学到了一些只有我不知道的常识 C-猫猫与数列_牛客小白月赛71(重现赛) (nowcoder.com) 题意: 思路: 直接模拟即可 值得注意的是,他在算数列的过程中可能会爆long long,因此在算的时候注意开__int128,这样…

微信小程序 开发中的问题(simba_wx)

目录 一、[将 proto 文件转成 json 文件](https://blog.csdn.net/wzxzRoad/article/details/129300513)二、[使用 test.json 文件](https://blog.csdn.net/wzxzRoad/article/details/129300513)三、[微信小程序插件网址](https://ext.dcloud.net.cn/)四、[vant-weapp网址](http…

为什么停更ROS2机器人课程-2023-

机器人工匠阿杰肺腑之言: 我放弃了ROS2课程 真正的危机不是同行竞争,比如教育从业者相互竞争不会催生ChatGPT…… 技术变革的突破式发展通常是新势力带来的而非传统行业的升级改革。 2013年也就是10年前在当时主流视频网站开启分享: 比如 …

电脑开机后出现哭脸错误无法启动解决方法

电脑开机后出现哭脸错误无法启动解决方法。有用户安装好电脑系统之后,遇到了哭脸错误的情况。出现这样的错误原因有很多。如果你无法找到问题的根源的话,其实都是可以通过U盘重装系统的方法来解决的?接下来我们一起来看看以下的操作教学吧。 …

BM39-序列化二叉树

题目 请实现两个函数,分别用来序列化和反序列化二叉树,不对序列化之后的字符串进行约束,但要求能够根据序列化之后的字符串重新构造出一棵与原二叉树相同的树。 二叉树的序列化(Serialize)是指:把一棵二叉树按照某种遍历方式的结…

flv怎么转换成mp4?这3种方法总有适合你的

flv怎么转换成mp4?首先我们得知道flv为什么转换成mp4?FLV和MP4都是常用的视频格式,其中FLV格式在以前的一些互联网应用中得到了广泛使用。但随着技术的发展和设备的普及,MP4格式逐渐成为了主流的视频格式。因此,将FLV格…

jQuery 在图片和文字中插入内容(多种情况考虑)

昨天接到一个新的需要,在后台文章编辑器中,每一个文章的正文前面,可以单独添加一个电头字段,但是如果在富文本编辑器中最上面就添加图片的话,图片就会把电头和正文中的文字给隔开。需要做的是获取到电头字段&#xff0…

一款纯Web化免费SQL工具,重新定义数据库管理

SQL Studio是一款由麦聪软件研发的多数据库管理工具,提供Windows、Linux 和 MacOS三种版本的软件包,支持中英文两种语言。SQL Studio是用Java编写的,默认使用 JDK 8进行编译。 下载看这里: [SQLStudio] (http://www.maicongs.com/#/home/web)…

shell脚本function传参的使用

这里直接上操作来说明function 的传参 新建一个脚本 vi 1.sh #!/bin/bash function check_a {echo $2echo $3echo "this is check_a" } function check_b {echo $2echo "this is check_b" } $1 #$1作为选择执行哪个function的参数 执行以下这个脚本传…

测试不够快?试试这招!让pytest多进程/多线程执行测试用例,轻松提升测试效率!

目录:导读 前言: 多进程执行用例之pytest-xdist pytest-xdist分布式测试的原理: pytest-xdist分布式测试的流程: 多线程执行用例之pytest-parallel 常用参数配置 pytest-parallel与pytest-xdist对比说明: 结语 前言&#…

vue修改当前页面query参数

最近在项目中手写分页器(为什么手写,因为对分页器样式外观要求比较严苛),遇到一个需求,就是我们在点击分页的时候,再进入详情,之后回退希望能够回到之前的页码值。 解决这个需求,个…

研报精选230425

目录 【行业230425华金证券】电子行业动态分析:23Q1电子板块环比略微下降,半导体设备重仓持股显著上升 【行业230425中泰证券】电力设备与新能源行业周报:特斯拉一季度储能收入高增,宁德时代发布凝胶态电池 【行业230425长城国瑞证…

行业分析| 视频监控——AI自动巡检

随着视频监控的普及,现在很多社区、工地、车间、厂区、超市、商铺、酒店、餐馆等场所都安装了视频监控系统。当安装的视频监控出现故障时,我们该如何进行简单的视频故障识别呢?如果只依靠人工对视频故障识别排查,工作量是相当大的…

动态规划猜法中外部信息简化的相关问题(上)

文章目录 1、Leetcode 312.戳气球(困难)1.1 题目描述1.2 思路分析1.3 代码实现1.4 启示 2、Leetcode 546.移除盒子(困难)2.1 题目描述2.2 思路分析2.3 代码实现 3、消除字符3.1 题目描述3.2 思路分析3.3 代码实现 1、Leetcode 312…

ARM buildroot 的引入

一、X210 的 bsp 介绍 1、嵌入式 linux 产品的 bsp 介绍 (1) 大部分的 ARM 架构的 linux 平台的 bsp 的内容和结构都是相似的。 (2) bsp 一般是芯片厂家/板卡厂家提供的。 2、X210 的 linuxQT bsp 整体介绍 (1) tslib_x210_qtopia.tgz 是用来支持 QT 的触摸屏操作的应用层库。…

操作系统笔记——绪论

第一章绪论 1.1 操作系统的基本概念 1.1.1计算机硬件的基本组成 (冯 ~诺伊曼模型) ,由运算器,存储器,控制器,输入设备,输出设备组成。 引入操作系统的目的:提供一个计算机用户与…

【2. 初学ROS,年轻人的第一个Node节点】

【2. 初学ROS,年轻人的第一个Node节点】 1. 工作空间设置2. 创建Package3. 回访依赖包4. 创建Node节点5. 源码编译6. 运行Node节点7. Node节点完善8. 总结 本教程是B站阿杰视频的笔记 视频地址:https://www.bilibili.com/video/BV1nG411V7HW 超声波传感器…

streamx平台部署

一. streamx介绍 StreamPark 总体组件栈架构如下, 由 streampark-core 和 streampark-console 两个大的部分组成 , streampark-console 是一个非常重要的模块, 定位是一个综合实时数据平台,流式数仓平台, 低代码 ( Low Code ), Flink & Spark 任务托…