JS继承的实现方式一共有八种。下面我们来一个一个瞅一下。注意️:文章依据个人理解,难免有问题,还望各位及时指出!!!!!

  • 原型链继承
  • 借用构造函数继承
  • 组合继承
  • 原型式继承
  • 寄生继承
  • 寄生组合式继承
  • 原型拷贝和构造函数实现继承
  • Class继承
  • 混入方式继承多个对象

我们先创建一个父类

  // 父类
function Animal(name, color){
this.name = name;
this.attribute = {
color: color,
}
this.action = function (currentAction) {
console.log(this.name + currentAction)
}
}

原型链继承

实现

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

    function OrangeCat(){};
OrangeCat.prototype = new Animal('橘猫','橘色');
// 相当于OrangeCat.prototype.__proto__ = new Animal('橘猫','橘色').__proto__;
// __proto__是系统变量,可以省略
let firstOrangeCat = new OrangeCat();

缺陷

  • 缺少constructor,需要手动添加
  • 引用类型的属性被所有子类实例共享
  • 子类实例化时无法向父类构造函数传参

缺少constructor

我们直接打印一下OrangeCat,会发现缺少constructor,我们可以使用OrangeCat.prototype.constructor手动添加上constructor

引用类型的属性被所有子类实例共享

让我们来看一下下面的例子

    function OrangeCat(){}
OrangeCat.prototype = new Animal('橘猫','橘色');
// 第一只橘猫
let firstOrangeCat = new OrangeCat();
// 第二只橘猫
let secondOrangeCat = new OrangeCat();
console.log('第一只橘猫的颜色:' + firstOrangeCat.attribute.color);
console.log('第二只橘猫的颜色:' + secondOrangeCat.attribute.color);
// 将第一只橘猫的颜色改为黑色
firstOrangeCat.attribute.color = 'black';
console.log('颜色改变后第一只橘猫的颜色:' + firstOrangeCat.attribute.color);
console.log('颜色改变后第二只橘猫的颜色:' + secondOrangeCat.attribute.color);

结果:

图解

借用构造函数继承

实现

原理: 使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型),可以实现多继承(call多个父类对象)

  function YellowDog(name, color) {
Animal.call(this, name, color);
}
let firstYellowDog = new YellowDog('狗', '黄');

缺陷

  • 只能继承父类的实例属性和方法,不能继承原型属性/方法
  console.log(firstYellowDog instanceof Animal); // false
console.log(firstYellowDog instanceof YellowDog); // true
  • 无法实现复用,每个子类都有父类实例函数的副本,影响性能

图解

新创建一个BlackDog子类

    function BlackDog(name, color) {
Animal.call(this, name, color);
 }

组合继承

实现

原理:组合原型链继承和借用构造函数继承,用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。

解决了原型链继承中父类引用类型的属性被所有子类实例共享问题以及借用构造函数继承中只能继承父类的实例属性和方法却不能继承原型属性/方法的问题,使子类实例共享引用对象子类实例既不共享父类的引用类型的数据,也继承了原型。

如何解决父类引用类型的属性被所有子类实例共享问题?

因为构造函数会将属性附加到子类实例上,访问属性的时候直接会访问子类实例上的属性,相当于子类实例上的属性直接屏蔽了原型上的属性,避免了共享个问题的出现

  function Pig(name, color) {
Animal.call(this, name, color);
}
Pig.prototype = new Animal();
Pig.prototype.constructor = Pig;
let firstPig = new Pig('猪', '白');

缺陷

  • 由于调用了两次Animal,会导致有重复属性
 console.log(firstPig)

  • 每个子类都有父类实例函数的副本,影响性能

图解

原型式继承

实现

利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。

实现1:

  let cattle = {
name:'牛',
attribute: {
color: '黄',
}
}
let firstCattle = Object.create(cattle);

实现2:

  function object(obj){
function F(){};
F.prototype = obj;
return new F();
}
let cattle = {
name:'牛',
attribute: {
color: '黄',
}
}
let firstCattle = object(cattle);

缺陷

  • 引用类型的属性被实例共享
    let secondCattle = object(cattle);
console.log(firstCattle.attribute); // 黄
console.log(secondCattle.attribute); // 黄
firstCattle.attribute.color = '红';
console.log(firstCattle.attribute); // 红
console.log(secondCattle.attribute); // 红
  • 子类实例化时无法传参

图解

寄生继承

实现

在原型式继承的基础上,增强对象,返回构造函数。

  let sheep = {
name: '羊',
action: (currrentAction)=>{
console.log(currrentAction)
}
}
function createSheep(params) {
let clone = object(params);// 此处的object就是上文中原型式继承的object方法
clone.say = ()=>{
console.log('咩咩咩');
}
return clone;
}
let anSheep = createSheep(sheep);

缺陷

  • 引用类型的属性被实例共享(可参考原型式继承)
  • 子类实例化时无法传参

图解

寄生组合式继承

实现

结合借用构造函数传递参数和寄生模式实现继承。

只调用了一次Animal构造函数,因此避免了在Chicken.prototype 上创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和isPrototypeOf()。这是最成熟的方法,也是现在库实现的方法

  function Chicken(name, color){
// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
Animal.call(this, name);
}
// 将父类原型指向子类
let clonePrototype = Object.create(Animal.prototype); // 创建对象,创建父类原型的一个副
clonePrototype.constructor = Chicken;// 增强对象,弥补因重写原型而失去的默认的constructor
Chicken.prototype = clonePrototype; // 将新创建的对象赋值给子类的原型 let firstChicken = new Chicken("鸡", "乌");

缺陷

  • 每个子类都有父类实例函数的副本,影响性能

图解

原型拷贝和构造函数实现继承

实现

结合借用构造函数传递参数和遍历父类的原型链循环赋值给子类原型链来实现继承。和组合继承以及寄生组合式继承一样会调用Amimal.call(),不同对是三者对原型链的处理方式不同

  function Fish(name, color){
Animal.call(this, name, color)
}
for(var key in Animal.prototype) {
Fish.prototype[key] = Animal.prototype[key]
}
Fish.prototype.constructor = Fish;
let firstFish = new Fish('鱼', '红');

缺陷

  • 不可遍历的属性不会被继承

图解

Class继承

实现

ES6提供的继承方式,其extends的实现和上述的寄生组合式继承方式一样.

  class Rabbit {
constructor(name) {
this.name = name;
}
action(currentAction){
console.log(`当前动作${currentAction}`)
}
}
class FirstRabbit extends Rabbit{
constructor(name){
super('兔子');
}
ownName(){
}
}
let firstRabbit = new FirstRabbit('小白兔')
console.log(firstRabbit)

我们来看下结果

我们可以看到class继承也是通过原型链实现的,实际上ES6的class只是一个语法糖。

混入方式继承多个对象

实现

通过借用构造函数继承和Object.assign()实现多继承。在寄生组合的基础上再进一步。

    // 混入方式实现多继承
function OthenClass(){}
function Tiger(){
Animal.call(this);
OthenClass.call(this);
}
// 继承一个类
Tiger.prototype = Object.create(Animal.prototype);
// 混合其它
Object.assign(Animal.prototype, OthenClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;

问题️

函数声明和类声明的区别

函数声明会提升,类声明不会。首先需要声明你的类,然后访问它,否则会抛出一个ReferenceError。

ES5继承和ES6继承的区别

  • ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.call(this)).
  • ES6的继承有所不同,实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this。因为子类没有自己的this对象,所以必须先调用父类的super()方法,否则新建实例报错。

特别注意️:

基于原型链实现的继承都存在引用类型的属性共享的问题,文中所讲的的不共享引用类型的属性仅指不共享父类引用类型的属性

参考

JS高级程序设计

简单易懂的JS继承图解的更多相关文章

  1. JS详细图解全方位解读this

    JS详细图解全方位解读this 对于this指向的理解中,有这样一种说法:谁调用它,this就指向谁.在我刚开始学习this的时候,我是非常相信这句话的.因为在一些情况下,这样理解也还算说得通.可是我 ...

  2. js继承

    js继承有5种实现方式: 继承第一种方式:对象冒充 function Parent(username){ this.username = username; this.hello = function ...

  3. js继承之call,apply和prototype随谈

    在js中,call,apply和prototype都可以实现对象的继承,下面我们看一个例子: function FatherObj1() { this.sayhello = "I am jo ...

  4. js继承精益求精之寄生式组合继承

    一.混合/组合继承的不足 上一篇JS继承终于混合继承,认真思考一下,发现其还是有不足之处的: 空间上的冗余:在使用原型链的方法继承父类的原型属性(Animal.prototype)的同时,也在子类的原 ...

  5. 老生常谈--Js继承小结

    一直以来,对Js的继承有所认识,但是认识不全面,没什么深刻印象.于是,经常性的浪费很多时间重新看博文学习继承,今天工作不是特别忙,有幸看到了http://www.slideshare.net/stoy ...

  6. Js继承小结

    Js继承小结 一直以来,对Js的继承有所认识,但是认识不全面,没什么深刻印象.于是,经常性的浪费很多时间重新看博文学习继承,今天工作不是特别忙,有幸看到了http://www.slideshare.n ...

  7. js继承实现

    JS实现继承可以分为:对象冒充和原型链继承 其中对象冒充又包括:临时变量,call 和 apply 临时变量方法: function Person(name,sex){ this.name = nam ...

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

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

  9. js继承之原型链继承

    面向对象编程都会涉及到继承这个概念,JS中实现继承的方式主要是通过原型链的方法. 一.构造函数.原型与实例之间的关系 每创建一个函数,该函数就会自动带有一个 prototype 属性.该属性是个指针, ...

随机推荐

  1. Python访问列表中的值

    Python访问列表中的值: 列表中可以包含所有数据类型: # 列表中可以存放 数字数据类型数据 # int 型数据 lst = [1,2,3] print(lst) # [1, 2, 3] # fl ...

  2. Python修改元组

    Python修改元组:元组中的元素值是不允许修改的,当创建好的时候就是固定不变的.所谓的修改其实是指创建一个新的元组,只是该元组可能是比原来的元组多一个元素或者少一个元素,然后使用新创建好的元组代替原 ...

  3. python基础day6_字典dict

    数据类型划分:可变数据类型.不可变数据类型 不可变数据类型(又叫可哈希):元祖,bool ,int,str, 可变数据类型(又叫不可哈希):list,dict,set(集合) dict的key必须是不 ...

  4. PHP strftime() 函数

    ------------恢复内容开始------------ 实例 根据区域设置格式化本地日期和时间: <?php echo(strftime("%B %d %Y, %X %Z&quo ...

  5. 不用注入方式使用Spring管理的对象中的方法,神奇

    在小冷工作中遇到这么一个小问题,当你的业务层对象交给spring管理之后,在普通的类中调用这个类中的方法时候,会有个问题这个类在调用时候会一直返回一个null,而且还会抛出一个空指针异常. 小冷在遇到 ...

  6. MySQL一主多从配置和读写分离配置

    一.一主多从配置 此次操作实现的是一主两从的方式.主服务器slave2(2.100),从服务器slave2-1(2.107),slave2-2(2.108);第一:准备主数据库    1. 在不同的机 ...

  7. 利用python进行数据分析PDF高清完整版免费下载|百度云盘|Python基础教程免费电子书

    点击获取提取码:hi2j 内容简介 [名人推荐] "科学计算和数据分析社区已经等待这本书很多年了:大量具体的实践建议,以及大量综合应用方法.本书在未来几年里肯定会成为Python领域中技术计 ...

  8. NTFS 文件系统结构

    背景 NTFS 作为一个新的文件系统,因其安全性高而受到越来越多的重视,越来越多的应用采用了NTFS 文件系统.作为一个新的文件系统,NTFS 有着许多区别于FAT32 的优点,如磁盘配额.文件系统加 ...

  9. 用Python绘制一套“会跳舞”的动态图形给你看看

    在读技术博客的过程中,我们会发现那些能够把知识.成果讲透的博主很多都会做动态图表.他们的图是怎么做的?难度大吗?这篇文章就介绍了 Python 中一种简单的动态图表制作方法. 看这优美的舞姿 很多人学 ...

  10. JavaFX桌面应用-MVC模式开发,“真香”

    使用mvc模块开发JavaFX桌面应用在JavaFX系列文章第一篇 JavaFX桌面应用开发-HelloWorld 已经提到过,这里单独整理使用mvc模式开发开发的流程. ~ JavaFX桌面应用开发 ...