JS 常见的 6 种继承方式

news2024/9/22 2:22:37

原型链继承

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

function Parent1() {
    this.name = 'parent1';
    this.play = [1, 2, 3]
}
function Child1() {
    this.type = 'child2';
}
Child1.prototype = new Parent1();
console.log(new Child1());

上面的代码其实有一个潜在的问题,例如:

var s1 = new Child1();
var s2 = new Child1();
s1.play.push(4);
console.log(s1.play);
console.log(s2.play);

执行结果如下:

当我修改了s1的play属性的时候,s2的play属性也跟着变了,因为两个实例使用的是同一个原型对象。它们的内存空间是共享的,当一个发生变化的时候,另外一个也随之进行了变化,这就是使用原型链继承方式的一个缺点。

构造函数继承(借助 call)

function Parent1(){
    this.name = 'parent1';
}

Parent1.prototype.getName = function () {
    return this.name;
}

function Child1(){
    Parent1.call(this);
    this.type = 'child1'
}

let child = new Child1();
console.log(child);  // 没问题
console.log(child.getName());  // 会报错

运行结果如下:

除了 Child1 的属性 type 之外,也继承了 Parent1 的属性 name。这样写的时候子类虽然能够拿到父类的属性值,解决了第一种继承方式的弊端,但问题是,父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法。

因此构造函数实现继承的优缺点,它使父类的引用属性不会被共享,优化了第一种继承方式的弊端;但是随之而来的缺点也比较明显——只能继承父类的实例属性和方法,不能继承原型属性或者方法。

组合继承(前两种组合)

这种方式结合了前两种继承方式的优缺点,结合起来的继承,代码如下:

function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
  }
  Parent3.prototype.getName = function () {
    return this.name;
  }
  function Child3() {
    // 第二次调用 Parent3()
    Parent3.call(this);
    this.type = 'child3';
  }

  // 第一次调用 Parent3()
  Child3.prototype = new Parent3();
  // 手动挂上构造器,指向自己的构造函数
  Child3.prototype.constructor = Child3;
  var s3 = new Child3();
  var s4 = new Child3();
  s3.play.push(4);
  console.log(s3.play);  // 不互相影响
  console.log(s4.play);
  console.log(s3.getName()); // 正常输出'parent3'
  console.log(s4.getName()); // 正常输出'parent3'

结果如下:

之前方法一和方法二的问题都得以解决,但是这里又增加了一个新问题:通过注释我们可以看到 Parent3 执行了两次,第一次是改变Child3 的 prototype 的时候,第二次是通过 call 方法调用 Parent3 的时候,那么 Parent3 多构造一次就多进行了一次性能开销。

原型式继承

ES5 里面的 Object.create 方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。

let parent4 = {
    name: "parent4",
    friends: ["p1", "p2", "p3"],
    getName: function() {
        return this.name;
    }
};

let person4 = Object.create(parent4);
person4.name = "tom";
person4.friends.push("jerry");

let person5 = Object.create(parent4);
person5.friends.push("lucy");

console.log(person4.name);
console.log(person4.name === person4.getName());
console.log(person5.name);
console.log(person4.friends);
console.log(person5.friends);

执行结果如下:

通过 Object.create 这个方法可以实现普通对象的继承,不仅仅能继承属性,同样也可以继承 getName 的方法。前三个输出都是正常的,最后两个输出结果一致是因为Object.create 方法是可以为一些对象实现浅拷贝的,那么关于这种继承方式的缺点也很明显,多个实例的引用类型属性指向相同的内存。

寄生式继承

使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。

虽然其优缺点和原型式继承一样,但是对于普通对象的继承方式来说,寄生式继承相比于原型式继承,还是在父类基础上添加了更多的方法。实现如下:

let parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
        return this.name;
    }
};

function clone(original) {
    let clone = Object.create(original);
    clone.getFriends = function() {
        return this.friends;
    };
    return clone;
}

let person5 = clone(parent5);

console.log(person5.getName());
console.log(person5.getFriends());

输出结果如下:

从最后的输出结果中可以看到,person5 通过 clone 的方法,增加了 getFriends 的方法,从而使 person5 这个普通对象在继承过程中又增加了一个方法,这样的继承方式就是寄生式继承。

寄生组合式继承

结合第四种中提及的继承方式,解决普通对象的继承问题的 Object.create 方法,我们在前面这几种继承方式的优缺点基础上进行改造,得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式,代码如下:

function clone (parent, child) {
    // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;
}

function Parent6() {
    this.name = 'parent6';
    this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
    return this.name;
}
function Child6() {
    Parent6.call(this);
    this.friends = 'child5';
}

clone(Parent6, Child6);
    Child6.prototype.getFriends = function () {
    return this.friends;
}

let person6 = new Child6();
console.log(person6);
console.log(person6.getName());
console.log(person6.getFriends());

执行结果如下:

这种寄生组合式继承方式,基本可以解决前几种继承方式的缺点,较好地实现了继承想要的结果,同时也减少了构造次数,减少了性能的开销。整体看下来,这六种继承方式中,寄生组合式继承是这六种里面最优的继承方式。

ES6的extends关键字实现逻辑

ES6提供了extends语法糖,使用关键字很容易实现JavaScript的继承,先看一下extends使用方法。

class Person {
  constructor(name) {
    this.name = name
  }
  // 原型方法
  // 即 Person.prototype.getName = function() { }
  // 下面可以简写为 getName() {...}
  getName = function () {
    console.log('Person:', this.name)
  }
}
class Gamer extends Person {
  constructor(name, age) {
    // 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
    super(name)
    this.age = age
  }
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法

使用babel将ES6 的代码编译成 ES5,代码如下:

function _possibleConstructorReturn (self, call) { 
		// ...
		return call && (typeof call === 'object' || typeof call === 'function') ? call : self; 
}
function _inherits (subClass, superClass) { 
    // 这里可以看到
	subClass.prototype = Object.create(superClass && superClass.prototype, { 
		constructor: { 
			value: subClass, 
			enumerable: false, 
			writable: true, 
			configurable: true 
		} 
	}); 
	if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}

var Parent = function Parent () {
	// 验证是否是 Parent 构造出来的 this
	_classCallCheck(this, Parent);
};
var Child = (function (_Parent) {
	_inherits(Child, _Parent);
	function Child () {
		_classCallCheck(this, Child);
		return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
}
	return Child;
}(Parent));

从上面编译完成的源码中可以看到,它采用的也是寄生组合继承方式,因此也证明了这种方式是较优的解决继承的方式。

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

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

相关文章

On-Manifold Optimization: Local Parameterization

Overview Manifold Space vs Tangent Space Jacobian w.r.t Error State Jacobian w.r.t Error State vs True State According 1 2.4, The idea is that for a x ∈ N x \in N x∈N the function g ( δ ) : f ( x ⊞ δ ) g(\delta) : f (x \boxplus \delta) g(δ):f(x…

<C++> STL_deque

<c> STL_deque 1.deque的使用 deque(双端队列)&#xff1a;是一种双开口的"连续"空间的数据结构&#xff0c;双开口的含义是&#xff1a;可以在头尾两端进行插入和 删除操作&#xff0c;且时间复杂度为O(1)&#xff0c;与vector比较&#xff0c;头插效率高&a…

字节码操作的手术刀-Javassist

Javassist 前面文章介绍的 ASM 入门门槛还是挺高的&#xff0c;需要跟底层的字节码指令打交道&#xff0c;优点是小巧、性能好。Javassist 是一个性能比 ASM 稍差但是使用起来简单很多的字节码操作库&#xff0c;不需要了解字节码指令&#xff0c;由东京工业大学的数学和计算机…

求解整数规划问题的割平面法和分支定界法

文章目录 整数规划割平面法分支定界法代码实现 整数规划 整数规划问题是优化变量必须取整数值的线性或非线性规划问题&#xff0c;不过&#xff0c;在大多数情况下&#xff0c;整数规划问题指的是整数线性规划问题。 其数学模型为 m i n f ( x ) c T x s.t A x b x ≥ 0 x…

opencv的haarcascade_frontalface_default.xml等文件

文章目录 GitHub下载在安装好的OpenCV文件夹下寻找opencv-python中获取 GitHub下载 下载地址&#xff1a;https://github.com/opencv/opencv/tree/master/data/haarcascades 在安装好的OpenCV文件夹下寻找 路径如下&#xff1a; 你安装的opencv路径\OpenCV\opencv\build\et…

亮点!视频云存储/安防监控视频智能分析平台睡岗离岗检测

在生产过程中&#xff0c;未经领导允许的擅自离岗、睡岗会带来很多的潜在危害。TSINGSEE青犀推出的视频云存储/安防监控视频智能分析平台得睡岗离岗检测根据AI视频分析技术建立人工智能算法&#xff0c;对视频画面展开分析与识别。自动识别出人员睡岗、离岗、玩手机与抽烟等动作…

十六、pikachu之SSRF

文章目录 1、SSRF概述2、SSRF&#xff08;URL&#xff09;3、SSRF&#xff08;file_get_content&#xff09; 1、SSRF概述 SSRF(Server-Side Request Forgery&#xff1a;服务器端请求伪造)&#xff1a;其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能&…

c++ qt--线程(一)(第八部分)

c qt–线程&#xff08;一&#xff09;&#xff08;第八部分&#xff09; 一.进程&#xff08;Process&#xff09; 在任务管理器中的进程页下&#xff0c;可以看到进程&#xff0c;任务管理器将进程分为了三类&#xff0c;应用、后台进程、window进程 应用&#xff1a; 打开…

MyBatis-Plus 总结

MyBatis-Plus简介 官网&#xff1a;https://baomidou.com/ GitHub&#xff1a;https://github.com/baomidou/mybatis-plus Gitee&#xff1a;https://gitee.com/baomidou/mybatis-plus 简介 MyBatis-Plus &#xff08;简称 MP&#xff09;是一个 MyBatis的增强工具&#x…

【C++入门到精通】C++入门 —— 多态(抽象类和虚函数的魅力)

阅读导航 前言一、多态的概念1. 概念2. 多态的特点 二、多态的定义及实现1. 多态的构成条件2. 虚函数3. 虚函数的重写⭕虚函数重写的两个例外1.协变(基类与派生类虚函数返回值类型不同)2.析构函数的重写(基类与派生类析构函数的名字不同) 4. override 和 final&#xff08;C11 …

SLAM从入门到精通(开始篇)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多同学喜欢学习linux&#xff0c;但是他们只是把linux当成是一个嵌入式技术在学习&#xff0c;而不是当成工具在使用。平时&#xff0c;要么是自…

appium+python自动化测试

获取APP的包名 1、aapt即Android Asset Packaging Tool&#xff0c;在SDK的build-tools目录下。该工具可以查看apk包名和launcherActivity 2、在android-sdk里面双击SDK-manager,下载buidl-tools 3、勾选build-tools&#xff0c;随便选一个版本&#xff0c;我这里选的是24的版…

YOLOv5、v8改进:CrissCrossAttention注意力机制

目录 1.简介 2. yolov5添加方法&#xff1a; 2.1common.py构建CrissCrossAttention模块 2.2yolo.py中注册 CrissCrossAttention模块 2.3修改yaml文件。 1.简介 这是ICCV2019的用于语义分割的论文&#xff0c;可以说和CVPR2019的DANet遥相呼应。 和DANet一样&#xff0c;…

DBeaver的安装和使用:windows版

DBeaver官网下载地址&#xff1a;https://dbeaver.io/download/ 下载完成后&#xff0c; 进入傻瓜式安装&#xff1a; 这里会进入重复界面&#xff0c;一样点击下一步即可 选择安装目录&#xff0c;尽量不要选C盘&#xff0c; 我的电脑只有c盘&#xff0c; 没办法 等待安装完成…

linux操作系统的权限的深入学习(未完)

1.Linux权限的概念 Linux下有两种用户&#xff1a;超级用户&#xff08;root&#xff09;、普通用户。 超级用户&#xff1a;可以再linux系统下做任何事情&#xff0c;不受限制 普通用户&#xff1a;在linux下做有限的事情。 超级用户的命令提示符是“#”&#xff0c;普通用户…

MVSNet 和 PatchMatchNet 的DTU数据集 几个不同之处 一定要注意

文章目录 1 测试集 数据加载不同2 训练集 数量 分辨率不同 1 测试集 数据加载不同 1.MVSNet 的DTU测试数据集和PatchmatchNet测试数据集不一样&#xff1b; 区别在于数据加载&#xff0c;前者 cams文件最后是最小深度和间隔&#xff0c;后者是最小深度和最大深度。 2 训练集 …

layui框架学习(41:表单模块)

之前的文章《layui框架学习》14-16中介绍了通过预设类及部分layui属性设置表单的外观样式&#xff0c;layui中还提供有表单模块以对表单元素进行各类动态化渲染和相关操作&#xff0c;本文学习并记录表单模块form的常用属性、函数及事件的用法&#xff08;如果内容已在之前文章…

时序预测 | MATLAB实现SSA-XGBoost(麻雀算法优化极限梯度提升树)时间序列预测

时序预测 | MATLAB实现SSA-XGBoost(麻雀算法优化极限梯度提升树)时间序列预测 目录 时序预测 | MATLAB实现SSA-XGBoost(麻雀算法优化极限梯度提升树)时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 Matlab实现SSA-XGBoost时间序列预测&#xff0c;麻…

VS警告 C4819 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失

1、问题 Microsoft visual studio 2019跑仿真的时候出现该警告&#xff0c;在高级保存选项设置编码为Unicode(UTF-8 无签名)还是会有该警告。 2、解决方法 右键项目&#xff0c;打开属性设置&#xff0c;选中&#xff1a;工程 -> 右键选择"属性" -> C/C ->…

JSON文件读写教程【jsoncpp源码编译】【结尾附三方库下载链接】

目录 1 数据下载(jsoncpp源码)2 文件编译3 测试用例4 下载链接:内容: JSON文件的读取与保存可以使用jsoncpp库来实现,这里介绍该库的下载及编译方法。 1 数据下载(jsoncpp源码) 数据下载:Github地址 图1 github源码示意图 2 文件编译 2.1 点击Download ZIP,下载源码。 …