说实在话,以前我只需要知道“寄生组合继承”是最好的,有个祖传代码模版用就行。最近因为一些事情,几个星期以来一直心心念念想整理出来。本文以《JavaScript高级程序设计》上的内容为骨架,补充了ES6 Class的相关内容,从我认为更容易理解的角度将继承这件事叙述出来,希望大家能有所收获。

1. 继承分类

先来个整体印象。如图所示,JS中继承可以按照是否使用object函数(在下文中会提到),将继承分成两部分(Object.create是ES5新增的方法,用来规范化这个函数)。

其中,原型链继承和原型式继承有一样的优缺点,构造函数继承与寄生式继承也相互对应。寄生组合继承基于Object.create, 同时优化了组合继承,成为了完美的继承方式。ES6 Class Extends的结果与寄生组合继承基本一致,但是实现方案又略有不同。

下面马上进入正题。

2. 继承方式

上图上半区的原型链继承,构造函数继承,组合继承,网上内容比较多,本文不作详细描述,只指出重点。这里给出了我认为最容易理解的一篇《JS中的继承(上)》。如果对上半区的内容不熟悉,可以先看这篇文章,再回来继续阅读;如果已经比较熟悉,这部分可以快速略过。另,上半区大量借用了yq前端的一篇继承文章[1]。

2.1 原型链继承

核心:将父类的实例作为子类的原型


SubType.prototype = new SuperType()
// 所有涉及到原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向SuperType。
SubType.prototype.constructor = SubType;

优点:父类方法可以复用
缺点:

  • 父类的引用属性会被所有子类实例共享
  • 子类构建实例时不能向父类传递参数

2.2 构造函数继承

核心:将父类构造函数的内容复制给了子类的构造函数。这是所有继承中唯一一个不涉及到prototype的继承。


SuperType.call(SubType);

优点:和原型链继承完全反过来。

  • 父类的引用属性不会被共享
  • 子类构建实例时可以向父类传递参数

缺点:父类的方法不能复用,子类实例的方法每次都是单独创建的。

2.3 组合继承

核心:原型式继承和构造函数继承的组合,兼具了二者的优点。


function SuperType() {
this.name = 'parent';
this.arr = [1, 2, 3];
} SuperType.prototype.say = function() {
console.log('this is parent')
} function SubType() {
SuperType.call(this) // 第二次调用SuperType
} SubType.prototype = new SuperType() // 第一次调用SuperType

优点:

  • 父类的方法可以被复用
  • 父类的引用属性不会被共享
  • 子类构建实例时可以向父类传递参数

缺点:
调用了两次父类的构造函数,第一次给子类的原型添加了父类的name, arr属性,第二次又给子类的构造函数添加了父类的name, arr属性,从而覆盖了子类原型中的同名参数。这种被覆盖的情况造成了性能上的浪费。

2.4 原型式继承

核心:原型式继承的object方法本质上是对参数对象的一个浅复制。
优点:父类方法可以复用
缺点:

  • 父类的引用属性会被所有子类实例共享
  • 子类构建实例时不能向父类传递参数

function object(o){
function F(){}
F.prototype = o;
return new F();
} var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
}; var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"

ECMAScript 5 通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一 个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下, Object.create()与 object()方法的行为相同。——《JAVASCript高级编程》

所以上文中代码可以转变为


var yetAnotherPerson = object(person); => var yetAnotherPerson = Object.create(person);

2.5 寄生式继承

核心:使用原型式继承获得一个目标对象的浅复制,然后增强这个浅复制的能力。
优缺点:仅提供一种思路,没什么优点


function createAnother(original){
var clone=object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
} var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
}; var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

2.6 寄生组合继承

刚才说到组合继承有一个会两次调用父类的构造函数造成浪费的缺点,寄生组合继承就可以解决这个问题。


function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); // 创建了父类原型的浅复制
prototype.constructor = subType; // 修正原型的构造函数
subType.prototype = prototype; // 将子类的原型替换为这个原型
} function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
} SuperType.prototype.sayName = function(){
alert(this.name);
}; function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
// 核心:因为是对父类原型的复制,所以不包含父类的构造函数,也就不会调用两次父类的构造函数造成浪费
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
}

优缺点:这是一种完美的继承方式。

2.7 ES6 Class extends

核心: ES6继承的结果和寄生组合继承相似,本质上,ES6继承是一种语法糖。但是,寄生组合继承是先创建子类实例this对象,然后再对其增强;而ES6先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。


class A {} class B extends A {
constructor() {
super();
}
}

ES6实现继承的具体原理:


class A {
} class B {
} Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
} // B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype); // B 继承 A 的静态属性
Object.setPrototypeOf(B, A);

ES6继承与ES5继承的异同:
相同点:本质上ES6继承是ES5继承的语法糖
不同点:

  • ES6继承中子类的构造函数的原型链指向父类的构造函数,ES5中使用的是构造函数复制,没有原型链指向。
  • ES6子类实例的构建,基于父类实例,ES5中不是。

3. 总结

  • ES6 Class extends是ES5继承的语法糖
  • JS的继承除了构造函数继承之外都基于原型链构建的
  • 可以用寄生组合继承实现ES6 Class extends,但是还是会有细微的差别

参考文章:

[1]《js继承、构造函数继承、原型链继承、组合继承、组合继承优化、寄生组合继承
[2]《JavaScript高级编程》

原文地址:https://segmentfault.com/a/1190000015727237

一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends的更多相关文章

  1. 怎么理解js的原型链继承?

    前言 了解java等面向对象语言的童鞋应该知道.面向对象的三大特性就是:封装,继承,多态. 今天,我们就来聊一聊继承.但是,注意,我们现在说的是js的继承. 在js的es6语法出来之前,我们想实现js ...

  2. js 继承 原型链

    这里先说基于原型链实现的继承.那首先就得明白什么是原型链了: 每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针. 那么,假如我们让原型对象等于 ...

  3. JS继承——原型链

    许多OO语言支持两种继承:接口继承和实现继承.ECMAScript只支持实现继承,且继承实现主要依赖原型链实现. 原型链 基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法. 构造函数.原 ...

  4. 一篇文章理解JS数据类型、深拷贝和浅拷贝

    前言 笔者最近整理了一些前端技术文章,如果有兴趣可以参考这里:muwoo blogs.接下来我们进入正片: js 数据类型 六种 基本数据类型: Boolean. 布尔值,true 和 false. ...

  5. js继承之借用构造函数继承

    我的上一篇文章介绍了,原型链继承模式.但是单纯的原型链模式并不能很好地实现继承. 一.原型链的缺点 1.1 单纯的原型链继承最大的一个缺点,来自于原型中包含引用类型的值. 本来,我们没有通过原型链实现 ...

  6. 前端基本知识(二):JS的原型链的理解

    之前一直对于前端的基本知识不是了解很详细,基本功不扎实,但是前端开发中的基本知识才是以后职业发展的根基,虽然自己总是以一种实践是检验真理的唯一标准,写代码实践项目才是唯一,但是经常遇到知道怎么去解决这 ...

  7. 彻底理解什么是原型链,prototype和__proto__的区别以及es5中的继承

    再讲一遍好了( 参考https://blog.csdn.net/cc18868876837/article/details/81211729 https://blog.csdn.net/lc23742 ...

  8. Javascript 组合继承 原型链继承 寄生继承

    Javascript继承通常有三种方式. 第一种:组合式继承: function SuperType(name) { this.name = name; this.colors = ["re ...

  9. js javascript 原型链详解

    看了许多大神的博文,才少许明白了js 中原型链的概念,下面给大家浅谈一下,顺便也是为了巩固自己 首先看原型链之前先来了解一下new关键字的作用,在许多高级语言中,new是必不可少的关键字,其作用是为了 ...

随机推荐

  1. addEventListener 事件监听器 冒泡事件)

    addEventListener 事件监听器 (冒泡事件)   1.后面绑定的事件照样会执行 2.不会被覆盖 3.调用者是事件源but.addEventListener 4.参数1,事件名(不带on ...

  2. 前端工程师需要掌握的 Babel 知识

    在前端圈子里,对于 Babel,大家肯定都比较熟悉了.如果哪天少了它,对于前端工程师来说肯定是个噩梦.Babel 的工作原理是怎样的可能了解的人就不太多了.本文将主要介绍 Babel 的工作原理以及怎 ...

  3. Maven项目导出jar包,包含依赖

    1. Maven项目导出jar包,包含依赖:mvn dependency:copy-dependencies package 2. 可以在Project创建lib文件夹,输入以下命令:mvn depe ...

  4. centos7下面ruby的升级

    背景 在做redis集群时,所需要的使用ruby工具进行操作,发现在线安装的Ruby版本过低,redis支持的版本最少为2.2.2. 在线安装ruby 使用yum在线安装ruby,安装的版本为2.0. ...

  5. web工程中添加自建userLibary与将jar包放到lib文件夹下的区别

    纯 java项目 使用的本地自己的JRE,那么classLoader在加载jar和class时候是分开的,对于我们自己编写的class,会在 APP_HOME/bin下.导入的jar包或者user l ...

  6. Qt 插件学习(一)

    插件是什么 注意:这儿暂时不考虑静态插件(潜意识中总觉得它根本就不算插件). 插件是一个动态库(共享库).动态库是一个独立的文件中的独立模块,可被多个程序访问. 先看动态库的两种用法 1. 程序链接时 ...

  7. RunHelper,一个为跑步而设计的开源的android app

    RunHelper是一个为跑步而设计的android应用,意在为爱跑步的人提供一个简洁.实用.免费的工具. 我自己也经常跑步,也用过像Nike running.runkeeper之类的app:Nike ...

  8. MongoDB的安装及配置(Win7)

    一.下载MongoDB 登录Mongodb官网https://www.mongodb.com/download-center#community 二.安装MongoDB 安装真的比较简单 next就可 ...

  9. JAVA中注解的实现原理

    注解的本质 「java.lang.annotation.Annotation」接口中有这么一句话,用来描述『注解』. The common interface extended by all anno ...

  10. Django学习之缓存和信号

    Django学习之缓存和信号   一 缓存 由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views ...