目录

一、JS 实现继承的几种方式

第一种:原型链继承

二、构造函数继承(借助call方法)

三、组合继承(原型链继承+构造函数继承)

第四种:原型式继承(借助Object.create)

第五种:寄生式继承

第六种:寄生组合式继承

二、ES6 的 extends 关键字实现逻辑


继承可以使得子类别具有父类的各种方法和属性。先思考几个问题:

JS 的继承到底有多少种实现方式呢?ES6 的 extends 关键字是用哪种继承方式实现的呢?

一、JS 实现继承的几种方式

第一种:原型链继承

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

//第一种 原型链继承
function Parent(){this.wealth=['1W','2k','30w']this.fun=()=>{console.log("fun");}
}
function Child(){this.sex='1';
}
Child.prototype=new Parent();
let child1=new Child();
let child2=new Child();
console.log(child1.wealth===child2.wealth,child1.fun===child2.fun);// true  true
child1.fun.a=1;
console.log(child1.fun.a,child2.fun.a);// 1  1
child1.wealth.push('4k');
//[ '1W', '2k', '30w', '4k' ] [ '1W', '2k', '30w', '4k' ]
console.log(child1.wealth,child2.wealth);

可以看到,原型对象上的方法和属性都可以被实例对象访问,但是存在一个问题,多个实例对象共享一个原型对象,也就是说实例对象获取的原型对象上的属性和方法的内存空间是共享的,其中一个实例对象改变了它的原型对象,另外的实例对象获取原型对象身上的属性和方法也会发生改变,这是使用原型链继承的一个缺点

二、构造函数继承(借助call方法)

为了解决原型共享问题,构造函数继承方式借助call方法来解决

//第二种  构造函数继承(借助call方法)
function Parent(){this.wealth=['1w','2k','30w'];this.fun=()=>{console.log('fun');}
}
Parent.prototype.getWealth=function (){return this.wealth;
}
function Child(){Parent.call(this);this.sex='1';
}
let child1=new Child();
let child2=new Child();
console.log(child1.wealth===child2.wealth,child1.fun===child2.fun);//true true
child1.fun.a=1;
console.log(child1.fun.a,child2.fun.a);//1 undefined
child1.wealth.push('4k');
[ '1w', '2k', '30w', '4k' ] [ '1w', '2k', '30w' ]
console.log(child1.wealth,child2.wealth);
child1.getWealth();//报错

可以看到,构造函数继承能够解决原型链继承的弊端,但同时也产生了新的问题,子类只能继承父类实例对象的属性和方法,无法继承父类原型上的属性和方法

由此可以看出,以上两种继承方式各有优缺点,那么结合二者的优点,就产生了下面这种组合的继承方式

三、组合继承(原型链继承+构造函数继承)

//第三种   组合继承(原型链继承+构造函数继承)
function Parent(){this.wealth=['1k','2k','3w'];this.fun=()=>{console.log(fun);}
}
Parent.prototype.getWealth=function(){console.log(this.wealth);
}
function Child(){
//第二次调用parentParent.call(this);this.sex='1';
}
//继承原型链上的方法属性
//第一次调用parent
Child.prototype=new Parent();
//原型对象私有化
Child.prototype.constructor=Child;
let child1=new Child();
let child2=new Child();
console.log(child1.wealth===child2.wealth,child1.fun===child2.fun);//false  false
child1.fun.a=1;
console.log(child1.fun.a,child2.fun.a);//1 undefined
child2.wealth.push("4k");
console.log(child1.wealth, child2.wealth);
// ["1w", "2k", "30w"]  ["1w", "2k", "30w", "4k"]
child1.getWealth();// ["1w", "2k", "30w"] 

结合前两种方式的优缺点,能够解决前两种方式的问题,但同时也带来了新的问题,Parent构造函数执行了两次,第一次是改变Child的原型时,第二次是通过call方法调用Parent时,多了一次性能开销

是否有更好的解决办法呢?下面的第六种继承方式可以更好的解决这里的问题

上面介绍的更多是围绕构造函数的方式,那么对于JS的普通对象,要怎么实现继承呢?

第四种:原型式继承(借助Object.create)

这里不得不提到的就是 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);

第一个结果“tom”,比较容易理解,person4 继承了 parent4 的 name 属性,但是在这个基础上又进行了自定义。

第二个是继承过来的 getName 方法检查自己的 name 是否和属性里面的值一样,答案是 true。

第三个结果“parent4”也比较容易理解,person5 继承了 parent4 的 name 属性,没有进行覆盖,因此输出父对象的属性。

最后两个输出结果是一样的,讲到这里你应该可以联想到 02 讲中浅拷贝的知识点,关于引用数据类型“共享”的问题,其实 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 是通过寄生式继承生成的实例,它不仅仅有 getName 的方法,而且可以看到它最后也拥有了 getFriends 的方法,结果如下图所示。

第六种:寄生组合式继承

结合第四种中提及的继承方式,解决普通对象的继承问题的 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());

通过这段代码可以看出来,这种寄生组合式继承方式,基本可以解决前几种继承方式的缺点,较好地实现了继承想要的结果,同时也减少了构造次数,减少了性能的开销,我们来看一下上面这一段代码的执行结果。

可以看到 person6 打印出来的结果,属性都得到了继承,方法也没问题,可以输出预期的结果。

整体看下来,这六种继承方式中,寄生组合式继承是这六种里面最优的继承方式。另外,ES6 还提供了继承的关键字 extends,我们再看下 extends 的底层实现继承的逻辑。

二、ES6 的 extends 关键字实现逻辑

我们可以利用 ES6 里的 extends 的语法糖,使用关键词很容易直接实现 JavaScript 的继承,但是如果想深入了解 extends 语法糖是怎么实现的,就得深入研究 extends 的底层逻辑。

//ES6 的 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() // 成功访问到父类的方法

通过编译解码可以发现,extends内步也是采用的寄生组合继承方式。

总:

1、什么是继承?

答:子类去继承父类的东西,称之为继承;
如:子类继承父类的属性或方法等;

2、常见的继承方式有哪些?

答:继承方式有五种:
其一、原型链继承;
其二、构造函数继承(也称 call 继承);
其三、拷贝继承;
其四、组合继承(某一种或多种继承方式的组合);
其五、寄生式组合继承;

Ⅱ、五大继承的优缺点:

1、原型链继承的优缺点:

其一、优点:
A、实现相对简单;
B、通过子类实例可以直接访问父类原型链上和实例上的成员(即:实现了继承);

其二、缺点:
A、子类实例修改引用类型值,会影响其他子类实例(但若不是引用类型就没有问题);

2、构造函数继承 (也称为 call 继承) 的优缺点:

其一、优点:
A、实现相对简单;
B、解决了原型链继承的缺点(即:子类实例修改引用类型值,不会影响其他子类实例);
C、可以在子类实例中直接向父类构造函数传参;

其二、缺点:
A、无法继承父类原型上的成员与方法;

3、拷贝继承的优缺点:

其一、优点:
A、可以在子类中直接给父类传参;
B、通过子类实例可以直接访问父类原型链和实例的成员;

其二、缺点:
A、拷贝继承在循环过程中比较消耗内存;

4、组合继承的优缺点:

其一、优点:
A、可以在子类实例中直接向父类构造函数传参;
B、通过子类实例可以直接访问父类原型链和实例的成员;

其二、缺点:
A、执行父类函数中两次的逻辑; (即:代码性能不很好,因为有些成员或方法在父类的原型链上又重新加载了一次);

5、寄生式组合继承的优缺点:

其一、优点:
A、可以在子类实例中直接向父类构造函数传参;
B、通过子类实例可以直接访问父类原型链和实例的成员;
C、解决了组合继承的缺点(即:执行父类函数中两次的逻辑),代码的性能更优化;

缺点:
A、缺点是有的,但暂时还没了解到;

原文链接:https://blog.csdn.net/weixin_43405300/article/details/122112614

Js理解之路:Js常见的6中继承方式相关推荐

  1. 【JS继承】常见的7种继承方式

     自我介绍:大家好,我是吉帅振的网络日志:微信公众号:吉帅振的网络日志:前端开发工程师,工作4年,去过上海.北京,经历创业公司,进过大厂,现在郑州敲代码. JS继承专栏 1[JS继承]什么是JS继承? ...

  2. 探究JS常见的6种继承方式

    先看以下百科对(面向对象的继承)的解释! 通过以上精炼实用的解释,我们可以了解到继承的基本作用和功能!即可以使得子类具有父类的属性和方法或者重新定义.追加属性和方法等. 广告:帮忙点击>> ...

  3. [js高手之路]从原型链开始图解继承到组合继承的产生

    于javascript原型链的层层递进查找规则,以及原型对象(prototype)的共享特性,实现继承是非常简单的事情 一.把父类的实例对象赋给子类的原型对象(prototype),可以实现继承 1 ...

  4. JavaScript中常见的几种继承方式

    继承方式一:混入继承 "use strict";//object:A let Oa= {money: 'many money',resource: 'company,house', ...

  5. HTML弧度文本,[js高手之路] html5 canvas系列教程 - 文本样式(strokeText,fillText,measureText,textAlign,textBaseline)...

    canvas提供两种输出文本的方式: strokeText:描边文本 fillText:填充文本 fillStyle配合fillText使用,strokeStyle配合strokeText使用 str ...

  6. # vue.js 之 对vue.js基础理解

    vue.js 之 对vue.js基础理解 Vue构造器 1 . Vue.js是一个构造函数,编程中称之为构造器 2 . 每一个new Vue() 都是一个Vue构造函数的实例,这个过程叫做实例化 3 ...

  7. [js高手之路]设计模式系列课程-发布者,订阅者重构购物车

    发布者订阅者模式,是一种很常见的模式,比如: 一.买卖房子 生活中的买房,卖房,中介就构成了一个发布订阅者模式,买房的人,一般需要的是房源,价格,使用面积等信息,他充当了订阅者的角色 中介拿到卖主的房 ...

  8. 性能提升三倍!优酷Node.js重构之路

    作者 | 阿里文娱前端技术专家 狼叔 在 2017 年底,优酷只有 Passport 和土豆的部分页面用 Node.js,PC 和 H5 核心页面还都是 PHP 模板渲染.而最近 2 年,基于阿里巴巴 ...

  9. 前端画圆弧html弧线的像素,[js高手之路] html5 canvas系列教程 - arc绘制曲线图形(曲线,弧线,圆形)...

    arc:画弧度 cxt.arc( x, y, 半径, 开始角度,结束角度,是否逆时针 ); x, y: 为弧度的中心横坐标和纵坐标,如果这是画一个圆.那么x,y就是圆的圆心. 开始角度与结束角度都是以 ...

最新文章

  1. guava Throwables类文档翻译及用法入门
  2. 单轴步进驱动模块SH-20403
  3. 西电oj1066 费马小定理
  4. Uva 1103 Ancient Messages
  5. python中文件读取不出来_Python文件读取
  6. webstorm 设置jsp支持
  7. android的progressDialog 的使用。android数据异步加载 对话框提示
  8. Mysql 中 delete 与 left join 的问题
  9. JavaTPoint Java 中文教程【翻译完成】
  10. 计算机组成原理—Cache和主存的映射模式
  11. 2021综述:计算机视觉中的注意力机制(续二):空间注意力
  12. cad批量打印快捷键_批量打印CAD图(无删减版)
  13. C#报错Newtonsoft.Json.JsonSerializationException: “A member with the name ‘phone‘ already
  14. HDU 6080 2017百度之星程序设计大赛 - 资格赛
  15. I'm just a baby——8月份英语总结
  16. devise校验旧密码
  17. 大数据技术有什么特点
  18. Python3 Django的补充
  19. html中url英文全称,URL的英文全称
  20. 关于Springboot定时任务

热门文章

  1. 16进制编辑器MadEdit
  2. 永中科技破产的主因是“重研发,轻市场”吗?
  3. windows系统——文件服务器简介、分类及安装步骤
  4. CSGO打开黑屏闪退
  5. 手机wps如何转换html,手机版wpsoffice怎么转换成电脑版
  6. 【天光学术】行政管理论文:事业单位行政管理机制创新策略探究(节选)
  7. 专精特新企业申报条件
  8. 设计模式详解——工厂模式(二)
  9. 祖冲之算法C语言程序加密txt文本,有运行方法和结果截图
  10. java gui jlabel_Java GUI编程4---标签组件JLabel