JS继承有哪些,你能否手写其中一两种呢?

news2024/11/29 1:31:55

引言

JS系列暂定 27 篇,从基础,到原型,到异步,到设计模式,到架构模式等,

本篇是 JS系列中第 3 篇,文章主讲 JS 继承,包括原型链继承、构造函数继承、组合继承、寄生组合继承、原型式继承、 ES6 继承,以及 多继承与 new 。

ES5 继承

先定义一个父类

function SuperType () {
  // 属性
  this.name = 'SuperType';
}
// 原型方法
SuperType.prototype.sayName = function() {
  return this.name;
};

一、 原型链继承

基本思想

将父类的实例作为子类的原型

// 父类
function SuperType () {
  this.name = 'SuperType'; // 父类属性
}
SuperType.prototype.sayName = function () { // 父类原型方法
  return this.name;
};

// 子类
function SubType () {
  this.subName = "SubType"; // 子类属性
};

SubType.prototype = new SuperType(); // 重写原型对象,代之以一个新类型的实例
// 这里实例化一个 SuperType 时, 实际上执行了两步
// 1,新创建的对象复制了父类构造函数内的所有属性及方法
// 2,并将原型 __proto__ 指向了父类的原型对象

SubType.prototype.saySubName = function () { // 子类原型方法
  return this.subName;
}

// 子类实例
let instance = new SubType();

// instanceof 通过判断对象的 prototype 链来确定对象是否是某个类的实例
instance instanceof SubType; // true
instance instanceof SuperType; // true

// 注意
SubType instanceof SuperType; // false
SubType.prototype instanceof SuperType ; // true

原型链继承

特点:

利用原型,让一个引用类型继承另一个引用类型的属性及方法

优点:

继承了父类的模板,又继承了父类的原型对象

缺点:

  • 可以在子类构造函数中,为子类实例增加实例属性。如果要新增原型属性和方法,则必须放在 SubType.prototype = new SuperType('SubType'); 这样的语句之后执行。

  • 无法实现多继承

  • 来自原型对象的所有属性被所有实例共享

  • 参考 前端进阶面试题详细解答

    // 父类
    function SuperType () {
      this.colors = ["red", "blue", "green"];
      this.name = "SuperType";
    }
    // 子类
    function SubType () {}
    
    // 原型链继承
    SubType.prototype = new SuperType();
    
    // 实例1
    var instance1 = new SubType();
    instance1.colors.push("blcak");
    instance1.name = "change-super-type-name";
    console.log(instance1.colors); // ["red", "blue", "green", "blcak"]
    console.log(instance1.name); // change-super-type-name
    // 实例2
    var instance2 = new SubType();
    console.log(instance2.colors); // ["red", "blue", "green", "blcak"]
    console.log(instance2.name); // SuperType
    

    prototype-shared

    注意:更改 SuperType 引用类型属性时,会使 SubType 所有实例共享这一更新。基础类型属性更新则不会。

  • 创建子类实例时,无法向父类构造函数传参,或者说是,没办法在不影响所有对象实例的情况下,向超类的构造函数传递参数

二、 构造继承

基本思想:

在子类型的构造函数内部调用父类型构造函数。

注意:

  • 函数只不过是在特定环境中执行代码的对象,所以这里使用 apply/call 来实现。

  • 使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

// 父类
function SuperType (name) {
  this.name = name; // 父类属性
}
SuperType.prototype.sayName = function () { // 父类原型方法
  return this.name;
};

// 子类
function SubType () {
  // 调用 SuperType 构造函数
  SuperType.call(this, 'SuperType'); // 在子类构造函数中,向父类构造函数传参
  // 为了保证子父类的构造函数不会重写子类的属性,需要在调用父类构造函数后,定义子类的属性
  this.subName = "SubType"; // 子类属性
};
// 子类实例
let instance = new SubType(); // 运行子类构造函数,并在子类构造函数中运行父类构造函数,this绑定到子类

构造函数继承

优点:

解决了1中子类实例共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数

缺点:

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

三. 组合继承

顾名思义,组合继承就是将原型链继承与构造函数继承组合在一起,从而发挥两者之长的一种继承模式。

基本思想:

使用原型链继承使用对原型属性和方法的继承,通过构造函数继承来实现对实例属性的继承。这样既能通过在原型上定义方法实现函数复用,又能保证每个实例都有自己的属性。

通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

// 父类
function SuperType (name) {
  this.colors = ["red", "blue", "green"];
  this.name = name; // 父类属性
}
SuperType.prototype.sayName = function () { // 父类原型方法
  return this.name;
};

// 子类
function SubType (name, subName) {
  // 调用 SuperType 构造函数
  SuperType.call(this, name); // ----第二次调用 SuperType----
  this.subName = subName;
};

// ----第一次调用 SuperType----
SubType.prototype = new SuperType(); // 重写原型对象,代之以一个新类型的实例

SubType.prototype.constructor = SubType; // 组合继承需要修复构造函数指向
SubType.prototype.saySubName = function () { // 子类原型方法
  return this.subName;
}

// 子类实例
let instance = new SubType('An', 'sisterAn')
instance.colors.push('black')
console.log(instance.colors) // ["red", "blue", "green", "black"]
instance.sayName() // An
instance.saySubName() // sisterAn

let instance1 = new SubType('An1', 'sisterAn1')
console.log(instance1.colors) //  ["red", "blue", "green"]
instance1.sayName() // An1
instance1.saySubName() // sisterAn1

组合继承1

第一次调用 SuperType 构造函数时,SubType.prototype 会得到两个属性namecolors;当调用 SubType 构造函数时,第二次调用 SuperType 构造函数,这一次又在新对象属性上创建了 namecolors,这两个属性就会屏蔽原型对象上的同名属性。

// instanceof:instance 的原型链是针对 SuperType.prototype 进行检查的
instance instanceof SuperType // true
instance instanceof SubType // true

// isPrototypeOf:instance 的原型链是针对 SuperType 本身进行检查的
SuperType.prototype.isPrototypeOf(instance) // true
SubType.prototype.isPrototypeOf(instance) // true

组合继承2

优点:

弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法,不存在引用属性共享问题,可传参,可复用

缺点:

  • 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

四. 寄生组合继承

在组合继承中,调用了两次父类构造函数,这里 通过通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

主要思想:

借用 构造函数 继承 属性 ,通过 原型链的混成形式 来继承 方法

// 父类
function SuperType (name) {
  this.colors = ["red", "blue", "green"];
  this.name = name; // 父类属性
}
SuperType.prototype.sayName = function () { // 父类原型方法
  return this.name;
};

// 子类
function SubType (name, subName) {
  // 调用 SuperType 构造函数
  SuperType.call(this, name); // ----第二次调用 SuperType,继承实例属性----
  this.subName = subName;
};

// ----第一次调用 SuperType,继承原型属性----
SubType.prototype = Object.create(SuperType.prototype)

SubType.prototype.constructor = SubType; // 注意:增强对象

let instance = new SubType('An', 'sisterAn')

寄生组合

优点:

  • 只调用一次 SuperType 构造函数,只创建一份父类属性
  • 原型链保持不变
  • 能够正常使用 instanceofisPrototypeOf

五. 原型式继承

实现思路:

实现思路就是将子类的原型设置为父类的原型

// 父类
function SuperType (name) {
  this.colors = ["red", "blue", "green"];
  this.name = name; // 父类属性
}
SuperType.prototype.sayName = function () { // 父类原型方法
  return this.name;
};

/** 第一步 */
// 子类,通过 call 继承父类的实例属性和方法,不能继承原型属性/方法
function SubType (name, subName) {
  SuperType.call(this, name); // 调用 SuperType 的构造函数,并向其传参 
  this.subName = subName;
}

/** 第二步 */
// 解决 call 无法继承父类原型属性/方法的问题
// Object.create 方法接受传入一个作为新创建对象的原型的对象,创建一个拥有指定原型和若干个指定属性的对象
// 通过这种方法指定的任何属性都会覆盖原型对象上的同名属性
SubType.prototype = Object.create(SuperType.prototype, { 
  constructor: { // 注意指定 SubType.prototype.constructor = SubType
    value: SubType,
    enumerable: false,
    writable: true,
    configurable: true
  },
  run : { 
    value: function(){ // override
      SuperType.prototype.run.apply(this, arguments); 
          // call super
          // ...
    },
    enumerable: true,
    configurable: true, 
    writable: true
  }
}) 

/** 第三步 */
// 最后:解决 SubType.prototype.constructor === SuperType 的问题
// 这里,在上一步已经指定,这里不需要再操作
// SubType.prototype.constructor = SubType;

var instance = new SubType('An', 'sistenAn')

原型继承1

多继承

如果希望能 多继承 ,可使用 混入 的方式

// 父类 SuperType
function SuperType () {}
// 父类 OtherSuperType
function OtherSuperType () {}

// 多继承子类
function AnotherType () {
    SuperType.call(this) // 继承 SuperType 的实例属性和方法
    OtherSuperType.call(this) // 继承 OtherSuperType 的实例属性和方法
}

// 继承一个类
AnotherType.prototype = Object.create(SuperType.prototype);

// 使用 Object.assign 混合其它
Object.assign(AnotherType.prototype, OtherSuperType.prototype);
// Object.assign 会把  OtherSuperType 原型上的函数拷贝到 AnotherType 原型上,使 AnotherType 的所有实例都可用 OtherSuperType 的方法

// 重新指定 constructor
AnotherType.prototype.constructor = AnotherType;

AnotherType.prototype.myMethod = function() {
     // do a thing
};

let instance = new AnotherType()

最重要的部分是:

  • SuperType.call 继承实例属性方法
  • Object.create() 来继承原型属性与方法
  • 修改 SubType.prototype.constructor的指向

ES6 继承

首先,实现一个简单的 ES6 继承:

class People {
    constructor(name) {
        this.name = name
    }
    run() { }
}

// extends 相当于方法的继承
// 替换了上面的3行代码
class Man extends People {
    constructor(name) {
        // super 相当于属性的继承
        // 替换了 People.call(this, name)
        super(name)
        this.gender = '男'
    }
    fight() { }
}

核心代码

extends 继承的核心代码如下,其实现和上述的寄生组合式继承方式一样

function _inherits(subType, superType) {
    // 创建对象,Object.create 创建父类原型的一个副本
    // 增强对象,弥补因重写原型而失去的默认的 constructor 属性
    // 指定对象,将新创建的对象赋值给子类的原型 subType.prototype
    subType.prototype = Object.create(superType && superType.prototype, {
        constructor: { // 重写 constructor
            value: subType,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    if (superType) {
        Object.setPrototypeOf 
            ? Object.setPrototypeOf(subType, superType) 
            : subType.__proto__ = superType;
    }
}

继承的使用场景

  • 不要仅仅为了使用而使用它们,这只是在浪费时间而已。
  • 当需要创建 一系列拥有相似特性的对象 时,那么创建一个包含所有共有功能的通用对象,然后在更特殊的对象类型中继承这些特性。
  • 应避免多继承,造成混乱。

注: 考虑到JavaScript的工作方式,由于原型链等特性的存在,在不同对象之间功能的共享通常被叫做 委托 - 特殊的对象将功能委托给通用的对象类型完成。这也许比将其称之为继承更为贴切,因为“被继承”了的功能并没有被拷贝到正在“进行继承”的对象中,相反它仍存在于通用的对象中。

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

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

相关文章

前端vue项目发送请求不携带cookie(vue.config.js和nginx反向代理)

一、本地环境——使用vue.config.js配置了跨域代理本来发现问题,是因为后台记录到接收到的sessionId一直在变化,导致需要在同一个sessionId下处理的逻辑无法实现。一开始以为是前后端分离跨域导致的,网上给出了解决方案:main.js中…

线程同步的实现

线程同步 同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。 "同"字从字面上容易理解为一起动作 其实不是,"同"字应是指协同、协助、互相配合。 如进程、线程同步,可理解为进程或线程A和B一…

USB子系统简述

引子:关于 lsusb 命令 lsusb 列出系统中所有的USB设备: Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hubBus 004 :表示第四个 usb 主控制器(机器上总共有四个 usb 主控制器,可以通过命令 lspci | g…

看完这篇文章终于弄明白了什么是 RocketMQ 的存储模型

RocketMQ 优异的性能表现,必然绕不开其优秀的存储模型 。这篇文章,笔者按照自己的理解 , 尝试分析 RocketMQ 的存储模型,希望对大家有所启发。1 整体概览首先温习下 RocketMQ 架构。整体架构中包含四种角色 :Producer :消息发布的…

基于Python深度学习的垃圾分类代码,用深度残差网络构建

垃圾分类 完整代码下载地址:基于Python深度学习的垃圾分类代码 介绍 这是一个基于深度学习的垃圾分类小工程,用深度残差网络构建 软件架构 使用深度残差网络resnet50作为基石,在后续添加需要的层以适应不同的分类任务模型的训练需要用生…

Qt扫盲-QSerialPort理论总结

QSerialPort理论总结一、概述二、使用流程1. 错误处理2. 阻塞串行端口编程3. 非阻塞串行端口编程三、信号四、注意事项一、概述 QSerialPort 类其实就是一个打开串口,进行串口通信传输数据的功能类。我们可以使用QSerialPortInfo帮助类获取有关可用串行端口的信息&…

JavaEE高阶---Spring AOP

一:什么是Spring AOP? 首先,AOP是一种思想,它是对某一类事情的集中处理。 如用户登录权限的效验,没学 AOP 之前,我们所有需要判断用户登录的页面,都要各自实现或调用验证的方法。然后有了 AOP …

【Linux进程间通信】

Linux进程间通信进程间通信介绍进程间通信的概念进程间通信的目的进程间通信的本质进程间通信的分类管道什么是管道匿名管道匿名管道的原理pipe函数匿名管道使用步骤匿名管道读写规则匿名管道的特点匿名管道的四种特殊情况匿名管道的大小命名管道命名管道的原理使用命令创建命名…

【浮点数在内存中的存储规则】

我们知道,整型在内存中的存储比较简单,在内存中都是以二进制来存储的。然而,浮点型在内存中的存储较为复杂。下面来详细探讨: 直接举一个例子: int main() { int n 9; float *pFloat (float *)&n; printf("…

工业树莓派解决传统数据设备数据上云问题

一、前言 工业4.0的浪潮下,许多中小型制造业企业渴望通过数字化转型谋求新的发展动力,然而,在转型之路上常常会面临一个问题:传统数据采集设备数量多、种类杂,不支持比较新颖的现场总线协议或者通信技术,最…

java 微服务框架介绍 SpringCloud Eureka注册中心 Nacos注册中心

为什么要学习微服务框架 认识微服务 服务架构演变 单体架构 分布式架构 微服务结构 SrpingCloud SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。 服务拆分及远程调用 服务拆分注意事项 我们查询的时候需要…

Java基础 —— 编程入门

一、比特(bit)和字节(byte)一个0或者一个1存储为一个比特(bit),是计算机中最小的存储单位。计算机中是最基本的存储单元是字节(byte)。每个字节由8个比特构成。计算机就是一系列的电路开关。每个开关存在两种状态:关(off)和开(on)。如果电路是开的,它的值…

Ubuntu物理真机提高访问速度

这里不适合小白用户,只是做出几点提醒。 iguge学术助手 纯Ubuntu真机,是没办法访问外部网络的,先用百度搜索iguge下载一个,安装在Firefox浏览器插件上(edge或者chrome也行)。 免费的不好用,建…

算法之初始动态规划

目录 前言: 初始动态规划 0-1背包问题 0-1背包问题升级版 问题:如何巧妙解决“双十一”购物是的凑单问题? 总结: 前言: 淘宝的“双十一”购物节有各种促销活动,比如“满 200 元减 50 元”。假设你女朋友…

SpringBoot使用 axis 实现webservice客户端(亲测可行)

目录一、webservice在线验证服务端接口地址二、使用 axis 实现webservice客户端代码示例2.1、服务端地址使用qq在线接口验证接口2.2、webservice客户端示例代码一、webservice在线验证服务端接口地址 qq 在线验证接口:http://www.webxml.com.cn/webservices/qqOnli…

[飞腾]Trace32使用概述

最近将多年来收集到的教学视频、国内外图书、源码等整理整合拿出来,涉及arm、Linux、python、信号完整性、FPFA、DSP、算法、stm32、单片机、制图、电子模块、kali、出版社图书等。资料目前约1.5TB。资料详情请参阅: 1.5TB电子工程师资料详细介绍https:/…

软件定义的存储时代即将结束

数据存储、安全性、保护和整体管理对于大多数组织的生存至关重要。 从软件定义的存储时代的结束到本地存储的回归,Nyriad的首席营收官概述了他对最新技术趋势的看法,并提供了他对2023年将会发生的预测。 从以CPU为中心的软件定义存储过渡到卸载辅助架构…

Java 开发环境配置 || Java 基础语法

Java 开发环境配置 在本章节中我们将为大家介绍如何搭建Java开发环境,以及不同系统下的环境变量怎么配置。 window系统安装java 下载JDK 首先我们需要下载java开发工具包JDK,下载地址:Java Downloads | Oracle 点击如下下载按钮&#xff…

阿里CCO:基于Hologres的亿级明细BI探索分析实践

作者:张乃刚(花名:隽驰),CCO数据开发 CCO是Chief Customer Officer的缩写,也是阿里巴巴集团客户体验事业部的简称。随着业务的多元化发展以及行业竞争的深入,用户体验问题越来越受到关注。CCO体验业务运营…

【前端】CSS进阶

四、选择器进阶 1.1后代选择器:空格 作用:根据HTML标签的嵌套关系,选择父元素后代中满足条件的元素 选择器语法:选择器1 选择器2{css} 结果: 在选择器1所找到标签的后代(儿子、孙子、重孙子…&#xf…