前言
    当面试官问你:你了解js哪些继承方式?es6的class继承是如何实现的?你心中有很清晰的答案吗?如果没有的话,可以通过阅读本文,帮助你更深刻地理解js的所有继承方式。
 
    js继承总共分成5种,包括构造函数式继承、原型链式继承、组合式继承、寄生式继承和寄生组合式继承。
 
构造函数式继承
 
    首先来看第一种,构造函数式继承,顾名思义,也就是利用函数去实现继承;
 
    假设我们现在有一个父类函数:
// 父类构造函数
function Parent(color) {
this.color = color;
this.print = function() {
console.log(this.color);
}
}

现在要编写一个子类函数来继承这个父类,如下:

// 子类构造函数
function Son(color) {
Parent.call(this, color);
}

上面代码可以看到,子类Son是通过Parent.call的方式去调用父类构造函数,然后把this对象传进去,执行父类构造函数之后,子类Son就拥有了父类定义的color和print方法。

调用一下该方法,输出如下:

// 测试
var son1 = new Son('red');
son1.print(); // red

var son2 = new Son('blue');
son2.print(); // blue
    可以看到son1和son2都正常继承了父类的print方法和各自传进去的color属性;
 
    以上就是构造函数式继承的实现了,这是最原始的js实现继承的方式;
 
    但是当我们深入想一下会发现,这种根本就不是传统意义上的继承!
 
 
    因为每一个Son子类调用父类生成的对象,都是各自独立的,也就是说,如果父类希望有一个公共的属性是所有子类实例共享的话,是没办法实现的。什么意思呢,来看下面的代码:
function Flower() {
this.colors = ['黄色', '红色'];
this.print = function () {
console.log(this.colors)
}
}

function Rose() {
Flower.call(this);
}

var r1 = new Rose();
var r2 = new Rose();

console.log(r1.print()); // [ '黄色', '红色' ]
console.log(r2.print()); // [ '黄色', '红色' ]

我们现在有一个基类Flower,它有一个属性colors,现在我们把某一个实例的colors值改一下:

r1.colors.push('紫色');

console.log(r1.print()); // [ '黄色', '红色', '紫色' ]
console.log(r2.print()); // [ '黄色', '红色' ]
    结果如上,显然,改变的只有r1的值,因为通过构造函数创造出来的实例对象中,所有的属性和方法都是实例内部独立的,并不会跟其他实例共享。
 
    总结一下构造函数的优缺点:
  • 优点:所有的基本属性独立,不会被其他实例所影响;
  • 缺点:所有希望共享的方法和属性也独立了,没有办法通过修改父类某一处来达到所有子实例同时更新的效果;同时,每次创建子类都会调用父类构造函数一次,所以每个子实例都拷贝了一份父类函数的内容,如果父类很大的话会影响性能;
原型链继承
 
    下面我们来看第二种继承方式,原型链式继承;
 
    同样先来看下例子:
function Parent() {
this.color = 'red';
this.print = function() {
console.log(this.color);
}
}
function Son() {
}

我们有一个父类和一个空的子类;

Son.prototype = new Parent();
Son.prototype.constructor = Son;

接着我们把子函数的原型属性赋值给了父函数的实例;

var son1 = new Son();
son1.print(); // red
    最后新建子类实例,调用父类的方法,成功拿到父类的color和print属性方法;
 
    我们重点来分析一下下面两行代码:
Son.prototype = new Parent();
Son.prototype.constructor = Son;
    这段代码中,子函数的原型赋给了父函数的实例,我们知道prototype是函数中的一个属性,js的一个特性就是:如果一个对象某个属性找不到,会沿着它的原型往上去寻找,直到原型链的最后才会停止寻找。
 
    关于原型更多基础的知识,可以参考一下其他文章,或许以后我也会出一期专门讲解原型和原型链的文章。
 
    回到代码,我们看到最后实例son成功调用了Print方法,输出了color属性,这是因为son从函数Son的prototype属性上面去找到的,也就是从new Parent这个对象里面找到的;
 
   
    这种方式也不是真正的继承,因为所有的子实例的属性和方法,都在父类同一个实例上了,所以一旦某一个子实例修改了其中的方法,其他所有的子实例都会被影响,来看下代码: 
function Flower() {
this.colors = ['黄色', '红色'];
this.print = function () {
console.log(this.colors)
}
}

function Rose() {}
Rose.prototype = new Flower();
Rose.prototype.constructor = Rose;

var r1 = new Rose();
var r2 = new Rose();

console.log(r1.print()); // [ '黄色', '红色' ]
console.log(r1.print()); // [ '黄色', '红色' ]

r1.colors.push('紫色');

console.log(r1.print()); // [ '黄色', '红色', '紫色' ]
console.log(r2.print()); // [ '黄色', '红色', '紫色' ]
    还是刚才的例子,这次Rose子类选择了原型链继承,所以,子实例r1修改了colors之后,r2实例的colors也被改动了,这就是原型链继承不好的地方。
 
    来总结下原型链继承的优缺点:
  • 优点:很好的实现了方法的共享;
  • 缺点:正是因为什么都共享了,所以导致一切的属性都是共享的,只要某一个实例进行修改,那么所有的属性都会变化 
组合式继承
 
    这里来介绍第三种继承方式,组合式继承;
 
    这种继承方式很好理解,既然构造函数式继承和原型链继承都有各自的优缺点,那么我们把它们各自的优点整合起来,不就完美了吗?
 

   
 
 
 
 
 
 
 
组合式继承做的就是这个事情~来看一段代码例子:
​function Parent(color) {
this.color = color;
}
Parent.prototype.print = function() {
console.log(this.color);
}
function Son(color) {
Parent.call(this, color);
}
Son.prototype = new Parent();
Son.prototype.constructor = Son;

var son1 = new Son('red');
son1.print(); // red

var son2 = new Son('blue');
son2.print(); // blue
    上面代码中,在Son子类中,使用了Parent.call来调用父类构造函数,同时又将Son.prototype赋给了父类实例;为什么要这样做呢?为什么这样就能解决上面两种继承的问题呢?
 
 
    我们接着分析一下,使用Parent.call调用了父类构造函数之后,那么,以后所有通过new Son创建出来的实例,就单独拷贝了一份父类构造函数里面定义的属性和方法这是前面构造函数继承所提到的一样的原理;
 
    然后,再把子类原型prototype赋值给父类的实例,这样,所有子类的实例对象就可以共享父类原型上定义的所有属性和方法。这也不难理解,因为子实例会沿着原型链去找到父类函数的原型。
 
    因此,只要我们定义父类函数的时候,将私有属性和方法放在构造函数里面,将共享属性和方法放在原型上,就能让子类使用了。
 
    以上就是组合式继承,它很好的融合了构造函数继承和原型链继承,发挥两者的优势之处,因此,它算是真正意义上的继承方式。
 
寄生式继承
 
    既然上面的组合式继承都已经这么完美了,为什么还需要其他的继承方式呢?
 
 
    我们细想一下,Son.prototype = new Parent();这行代码,它有什么问题没有?
 
    显然,每次我们实例化子类的时候,都需要调用一次父类构造函数,那么,如果父类构造函数是一个很大很长的函数,那么每次实例化子类就会执行很长时间。
 
    实际上我们并不需要重新执行父类函数,我们只是想要继承父类的原型。
 
    寄生式继承就是在做这个事情,它是基于原型链式继承的改良版:
 
var obj = {
color: 'red',
print: function() {
console.log(this.color);
}
};

var son1 = Object.create(obj);
son1.print(); // red

var son2 = Object.create(obj);
son2.print(); // red

寄生式继承本质上还是原型链继承,Object.create(obj);方法意思是以obj为原型构造对象,所以寄生式继承不需要构造函数,但是同样有着原型链继承的优缺点,也就是它把所有的属性和方法都共享了。

寄生组合式继承
 
    接下来到我们最后一个继承方式,也就是目前业界最为完美的继承解决方案:寄生组合式继承。
 
    没错,它就是es6的class语法实现原理。
 
    但是如果你理解了组合式继承,那么理解这个方式也很简单,只要记住,它出现的主要目的,是为了解决组合式继承中每次都需要new Parent导致的执行多一次父类构造函数的缺点。
 
    下面来看代码:
function Parent(color) {
this.color = color;
}
Parent.prototype.print = function() {
console.log(this.color);
}
function Son(color) {
Parent.call(this, color);
}
Son.prototype = Object.create(Parent.prototype);
Son.prototype.constructor = Son;

var son1 = new Son('red');
son1.print(); // red

var son2 = new Son('blue');
son2.print(); // blue
    这段代码不同之处只有一个,就是把原来的Son.prototype = new Parent();修改为了Son.prototype = Object.create(Parent.prototype);
 
    我们前面讲过,Object.create方法是以传入的对象为原型,创建一个新对象;创建了这个新对象之后,又赋值给了Son.prototype,因此Son的原型最终指向的其实就是父类的原型对象,和new Parent是一样的效果;
 
 
    到这里,我们5中js的继承方式也就讲完了;

 
 
 
 
 
 
 
 
 
    如果你对上面的内容感到疑问或者不理解的,可以留言和我交流,或者关注公众号直接联系我~
 
    最后感谢小伙伴的阅读,如果觉得文章写的还可以的话,欢迎点个赞、点个关注,我会持续输出优质的技术分享文章。​
 

都0202年了,你还不知道javascript有几种继承方式?的更多相关文章

  1. JavaScript的3种继承方式

    JavaScript的继承方式有多种,这里列举3种,分别是原型继承.类继承以及混合继承. 1.原型继承 优点:既继承了父类的模板,又继承了父类的原型对象: 缺点:不是子类实例传参,而是需要通过父类实例 ...

  2. Javascript的四种继承方式

    在Javascript中,所有开发者定义的类都可以作为基类,但出于安全性考虑,本地类和宿主类不能作为基类,这样可以防止公用访问编译过的浏览器级的代码,因为这些代码可以被用于恶意攻击. 选定基类后,就可 ...

  3. JavaScript的几种继承方式

    看<JavaScript高级程序设计>做的一些笔记 ECMAScript只支持实现继承,不支持接口继承(因为函数没有签名) 原型链(实现继承的主要方法): function SuperTy ...

  4. JavaScript之四种继承方式讲解

    在Javascript中,所有开发者定义的类都可以作为基类,但出于安全性考虑,本地类和宿主类不能作为基类,这样可以防止公用访问编译过的浏览器级的代码,因为这些代码可以被用于恶意攻击. 选定基类后,就可 ...

  5. JavaScript 常见的六种继承方式

    JavaScript 常见的六种继承方式 前言 面向对象编程很重要的一个方面,就是对象的继承.A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法.这对于代码的复用是非常有用的. 大部分 ...

  6. JavaScript的7种继承模式

    <JavaScript模式>一书中,对于JavaScript的几种继承模式讲解得很清楚,给我提供了很大帮助.总结一下,有如下7种模式. 继承模式1--设置原型(默认模式) 实现方式: // ...

  7. 【转】SVG与HTML、JavaScript的三种调用方式

    原文:https://www.cnblogs.com/guohu/p/5085045.html SVG与HTML.JavaScript的三种调用方式 一.在HTMl中访问SVG的DOM 1 2 3 4 ...

  8. JavaScript中七种函数调用方式及对应 this 的含义

    this 在 JavaScript 开发中占有相当重要的地位,不过很多人对this这个东西都感觉到琢磨不透.要真正理解JavaScript的函数机制,就非常有必要搞清楚this到底是怎么回事. 函数调 ...

  9. JavaScript常用八种继承方案

    更新:在常用七种继承方案的基础之上增加了ES6的类继承,所以现在变成八种啦,欢迎加高级前端进阶群一起学习(文末). --- 2018.10.30 1.原型链继承 构造函数.原型和实例之间的关系:每个构 ...

随机推荐

  1. sqlilab less15-17

    less15 试了很多符号,页面根本不显示别的信息,猜测为盲注 可是怎么检测闭合? 万能密码登录 最终试出来'闭合 uname=1' or 1=1 # 接下来就要工具跑 less16 同上用万能密码试 ...

  2. Mac安装Nginx、Mysql、PHP、Redis

    安装xcode命令行工具的命令 xcode-select --install   安装homebrew: ruby -e "$(curl -fsSL https://raw.githubus ...

  3. 作业3-k均值算法

    4. 作业: 1). 扑克牌手动演练k均值聚类过程:>30张牌,3类 2). *自主编写K-means算法 ,以鸢尾花花瓣长度数据做聚类,并用散点图显示.(加分题) 3). 用sklearn.c ...

  4. Linux系统管理第一次作业 系统命令

    上机作业: 1.请用命令查出ifconfig命令程序的绝对路径 [root@localhost ~]# which ifconfig  /usr/sbin/ifconfig 2.请用命令展示以下命令哪 ...

  5. 一款被大厂选用的 Hexo 博客主题

    首先这是一篇自吹自擂的文章,主题是由多位非前端程序员共同开发,目前经过一年半的迭代已经到达 v1.8.0 版本,并且获得大量认可,甚至某大厂员工已经选用作为内部博客,因此我决定写这篇文章向更多人安利它 ...

  6. 【DNS域名解析命令】 ping

    ping, ping6 - send ICMP ECHO_REQUEST to network hosts ping命令向网络主机发送ICMP回传请求 详细描述: ping使用ICMP协议强制ECHO ...

  7. Soldiers Sortie

    这部2006年上映的作品豆瓣评分9.3,时至今日仍然有人乐此不疲地讨论剧情和启发,我想称之为经典应该不算过分. 这部剧我至少完整看过3遍,随着阅历增加,对某些角色的体会也变得更深刻. 许三多 许三多是 ...

  8. 数据库SQL语言从入门到精通--Part 5--E-R图(实体联系图)用来描述数据库图例

    数据库从入门到精通合集(超详细,学习数据库必看) E-R图也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型.属性和联系的方法,用来描述现实世界的概念模型. ...

  9. Codeforces 1323 div2题解ABC

    A. Even Subset Sum Problem 签到题 #include <bits/stdc++.h> using namespace std; template <type ...

  10. Codeforce 1255 Round #601 (Div. 2)D. Feeding Chicken (模拟)

    Long is a huge fan of CFC (Codeforces Fried Chicken). But the price of CFC is increasing, so he deci ...