原文出自于本人个人博客网站:https://www.dzyong.com(欢迎访问)

本文链接地址: https://www.dzyong.com/#/ViewArticle/89(转载请标注来源)

设计模式系列博客:JavaScript设计模式系列目录(持续更新中)

什么是继承

继承是面向对象软件技术当中的一个概念。如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”也可以称“B是A的超类”。继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类别的行为在编译期就已经决定,无法在执行期扩充

了解了什么是继承后,接下来看一下在JavaScript实现继承的6种方式

子类的原型对象——类式继承

let superClass = function(){this.superVal = truethis.books = ['a', 'b', 'c']}superClass.prototype.getSuperVal = function(){return this.superVal}let sup = new superClass()//声明子类1let subClass = function(){this.subVal = false}subClass.prototype = new superClass()subClass.prototype.getSuperVal = function(){this.subVal}

类式继承就是声明两个类,只不过是把第一个类的实例赋值给第二个类的原型。

类的原型对象的作用就是为类的原型添加共有的方法,但类不能直接访问这些属性和方法,必须通过原型prototype来访问。而我们实例化一个父类的时候,新创建的对象复制了父类的构造函数与方法并将原型_proto_指向了父类的原型对象,这样就拥有了父类原型对象上的属性和方法,并且这个新创建的对象可直接访问到父类原型对象上的属性与方法。

let sub = new subClass1()
console.log(sub.superVal)   //true
console.log(sub.getSuperVal)   //false

我们可以使通过instanceof来检测某个对象是否是某个类的实例,instanceof是通过判断对象的prototype链来确定关系的,而不关心对象与类的自身结构。

console.log(sub instanceof superClass)    //true
console.log(sub instanceof subClass)    //true
console.log(subClass instanceof superClass)    //false

一定要注意:instanceof是判断前面的对象是否是后面类(对象)的实例,并不表示两者的继承关系。

console.log(subClass.prototype instanceof superClass)    //true

Object是所有对象的祖先

console.log(sub instanceof Object)    //true

但是类式继承有两个缺点:(1)由于子类是通过其原型prototype对父类的实例化,继承了父类。所以说父类中的共有属性要是引用类型,就会在子类被所有实例共用,因此一个子类的实例更改子类原型从父类构造函数中继承来的共有属性就会直接影响到其他子类。

let sub1 = new subClass()
let sub2 = new subClass()
console.log(sub1.books)   //['a', 'b', 'c']
sub2.books.push('d')
console.log(sub1.books)   ['a', 'b', 'c', 'd']

sub2的修改影响到了sub1的book属性。(2)由于子类实现继承是靠prototype对父类的实例化实现的,因此在创建父类的时候,是无法向父类传递参数的,因而是实例化父类的时候也无法对父类构造函数内的属性进行初始化。

创建即继承——构造函数继承

let superClass = function(id){this.books = ['JavaScript', 'html', 'css']this.id = id
}
superClass.prototype.showBooks = function(){console.log(this.books)
}
let subClass = function(id){superClass.call(this, id)
}
let instance1 = new subClass(10)
let instance2 = new subClass(11)
instance1.books.push('设计模式')
console.log(instance1.books)  //["JavaScript", "html", "css", "设计模式"]
console.log(instance2.books)  //["JavaScript", "html", "css"]

call这个方法可以更改函数的作用环境,对superClass调用这个方法就是将子类中的变量在父类中执行一遍,由于父类中是给this绑定属性的,因此子类自然就继承了父类的共有属性。

这种类型的继承没有涉及原型prototype,所以父类的原型方法自然不会被子类继承,而如果想被子类继承就必须要放在构造函数中,这样创建出来的每个实例都会单独拥有一份而不能共用,这样就违背了代码复用原则。为了综合这两种模式的有点,后来有了组合式继承。

将优点为我所用——组合继承

组合继承顾名思义就是将上面讲的两种继承方式组合起来用,综合各自的优点。

let supClass = function(name){this.name = namethis.books = ['JavaScript', 'html', 'css']
}
supClass.prototype.showBooks = function(){console.log(this.books)
}
let subClass = function(name, time){this.time = timesupClass.call(this, name)
}
subClass.prototype = new supClass()
let instance1 = new subClass('dzy', 2018)
let instance2 = new subClass('hxy', 2019)
console.log(instance1.name, instance1.time)   //dzy 2018
console.log(instance2.name, instance2.time)   //hxy 2019
instance1.books.push('设计模式')
instance1.showBooks()   //["JavaScript", "html", "css", "设计模式"]
instance2.showBooks()   //["JavaScript", "html", "css"]

在子类构造函数中执行父类构造函数,在子类原型上实例化父类就是组合模式,这样就融合了类式继承和构造函数继承的优点。

洁净的继承者——原型式继承

借助原型prototype可以根据已有的对象创建一个新的对象,同时不必创建新的自定义对象类型。

—— 道格拉斯·克罗克福德《JavaScript中原型式继承》

let inheritObject = function(o){//声明一个过渡函数对象function F(){}//过渡对象的原型继承父对象F.prototype = o//返回过渡对象的一个实例,该实例的原型继承了父对象return new F()
}

它是对类式继承的一个封装,其中的过渡对象就相当于类式继承中的子类,只不过在原型式中作为一个过渡对象出现,目的是为了创建要返回的新的实例化对象。

如虎添翼——寄生式继承

let book = {name: 'js book',alikeBook: ['JavaScript', 'html', 'css']
}
let createBook = function(obj){//通过原型继承方式创建新对象var o = new inheritObject(obj)//拓展新对象o.getName = function(){console.log(name)}return o
}

寄生式继承就是对原型继承的第二次封装,并在第二次封装过程中对继承的对象进行了拓展,这样新创建的对象不仅仅有父类中的属性和方法而且还添加新增属性和方法。

终极继承者——寄生组合式继承

在上面讲到过类式继承同构造函数继承组合使用,但是有一个问题,就是子类不是父类的实例,所以才有了寄生组合式继承。

寄生是寄生式继承,依托于原型继承,原型继承与类式继承相像。

let inheritProject = function(subClass, superClass){//复制一份父类的原型副本保存到变量中var p = inheritProject(superClass.prototype)//修正因为重写子类原型导致子类的constructor属性被次改p.constructor = subClass//设置子类原型subClass.prototype = p
}

组合式继承中,通过构造函数继承的属性和方法是没有问题的,所以这里我们主要探究通过寄生式继承重新继承父类的原型。我们需要继承的仅仅是父类的原型,不再需要调用父类的构造函数,换句话说,在构造函数继承中我们已经调用了父类的构造函数。因此我们需要的就是父类的原型对象的一个副本,而这个副本我们通过原型继承便可得到,但是这么直接赋值给子类会有问题的,因为对父类原型对象复制得到的复制对象p中的constructor 指向的不是subClass子类对象,因此在寄生式继承中要对复制对象p做次增强,修复其constructor属性指向不正确的问题,最后将得到的复制对象p赋值给子类的原型,这样子类的原型就继承了父类的原型并且没有执行父类的构造函数。

//定义父类
let supClass = function(name){this.name = namethis.colors = ['red', 'green', 'blue']
}
//定义父类原型方法
supClass.prototype.getName = function(){console.log(this.name)
}
//定义子类
let subClass = function(name, time){//构造函数式继承supClass.call(this, name)//子类新增属性this.time = time
}
//寄生式继承父类原型
inheritProject(subClass, superClass)
//子类新增原型方法
subClass.prototype.getTime = function(){console.log(this.time)
}
//创建两个测试方法
let instance1 = new subClass('js book', 2014)
let instance2 = new subClass('css book', 2013)
instance1.colors.push('black')
console.log(instance1.colors)    //['red', 'green', 'blue', 'black']
console.log(instance2.colors)    //['red', 'green', 'blue']
instance2.getName()   //css book
instance2.getTime()   //2013

这种继承方式如下图所示:

其中最大的改变就是对子类原型的处理,被赋予父类原型的一个引用,这是一个对象,这里要注意一点,就是子类再想添加原型方法必须通过prototype对象,否则知己赋予对象就会覆盖掉从父类原型继承的对象了。

本内容来源总结于《JavaScript设计模式》一书

原文出自于本人个人博客网站:https://www.dzyong.com(欢迎访问)

转载请注明来源: 邓占勇的个人博客 - 《JavaScript设计模式(2)—— 多种继承方式的实现及原理

本文链接地址: https://www.dzyong.com/#/ViewArticle/87

JavaScript设计模式(2)—— 多种继承方式的实现及原理相关推荐

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

    方式一.原型链继承 这种方式关键在于:子类型的原型为父类型的一个实例对象. -------------------------------------------------------------- ...

  2. javascript 的七种继承方式(三)组合继承

    组合继承 前面两篇我们了解到:原型链继承存在着引用类型问题,而借用构造函数又无法实现函数复用和原型方法继承的问题.那么能不能把两者结合一下,取其精华,弃其糟粕?答案是肯定的.那就是接下来我们要介绍的组 ...

  3. JavaScript中6种继承方式总结

  4. jQuery-源码阅读,JavaScript原生继承方式与jQuery中的继承

    JavaScript中继承方法有以下几种: 1.原型链继承: function Book (name,author){this.name=name;this.author=author;}Book.p ...

  5. 前端进击的巨人(七):走进面向对象,原型与原型链,继承方式

    "面向对象" 是以 "对象" 为中心的编程思想,它的思维方式是构造. "面向对象" 编程的三大特点:"封装.继承.多态" ...

  6. Js理解之路:Js常见的6中继承方式

    目录 一.JS 实现继承的几种方式 第一种:原型链继承 二.构造函数继承(借助call方法) 三.组合继承(原型链继承+构造函数继承) 第四种:原型式继承(借助Object.create) 第五种:寄 ...

  7. javascript 设计模式_用英雄联盟的方式讲解JavaScript设计模式(二)

    前言 大家好,这是第三篇作者对于设计模式的分享了,前两篇可以参考: 手写一下JavaScript的几种设计模式 (工厂模式,单例模式,适配器模式,装饰者模式,建造者模式) 用英雄联盟的方式讲解Java ...

  8. 【呆萌の研究】JavaScript常见的继承方式

    最近在看<JavaScript设计模式>,然后开篇复习了JavaScript中的几种继承方式,自己似乎也没有怎么仔细探究过,目前自己没怎么碰到过应用的场景(噗),所以借这次机会好好来屡屡思 ...

  9. JavaScript继承理解:ES5继承方式+ES6Class继承对比

    JavaScript中实现继承   在JavaScript中实现继承主要实现以下两方面的属性和方法的继承,这两方面相互互补,既有共享的属性和方法,又有特有的属性和方法. 实例属性和方法的继承: 目的: ...

  10. 【JavaScript设计模式张容铭】抽象工厂模式深度剖析——关于继承与constructor属性的深度理解

    写在前面 最近阅读了张容铭的<JavaScript设计模式>一书,阅读到抽象工厂模式一节时对书上的代码产生了疑问,但同时在解决疑问的过程中,对继承又产生了新的理解. 我仔细查阅了很多文章, ...

最新文章

  1. js 之 object
  2. Qstring 和 String相互转换
  3. 为程序员量身定做的目标
  4. TensorFlow损失函数(loss function) 2017-08-14 11:32 125人阅读 评论(0) 收藏 举报 分类: 深度学习及TensorFlow实现(10) 版权声明:
  5. scala Akka并发编程介绍
  6. 内核探测工具systemtap简介
  7. [转]根据时间(NSDate)获取具体的信息:月份、星期、天等
  8. python3 中的 eval 函数
  9. 计算机教案画圆形和方形,画方形和圆形的教案
  10. Java连接Elasticsearch6.xxx 环境测试篇一
  11. linux 服务管理
  12. osg加载osgb数据_铁路工程三维协同大数据云平台研究与开发
  13. 正向随机微分方程的经典数值格式模拟
  14. Java小峰宿舍管理系统_基于JAVA的宿舍管理系统
  15. 数字签名和数字证书的区别与联系
  16. android 合并两个图片大小,Android开始之将两张图片合并为一张图片的方法
  17. Appium-W3C Action(W3C动作)
  18. 用HTML+CSS做一个简单的新闻门户 1页网页
  19. hud 1560 DNA sequence(IDA* 迭代加深搜索+估值函数)
  20. MATLAB 之 优劣解距离法(TOPSIS )

热门文章

  1. jena java_java – 使用Jena查询wikidata
  2. “香港一卡通” 內地見證開戶
  3. docker file详细介绍
  4. PowerShell批量部署Hyper-V Windows虚机
  5. 基于K8S的容器化PaaS平台建设
  6. Android学习日记(yzy):Notification的简单运用
  7. 39. 网络管理之IP配置,查看当前IP和网络设备,网络配置文件(ifcfg-网卡)详解,配置静态IP,配置动态IP,临时设置,/etc/sysconfig/network-scripts/ifcfg
  8. 开通百度通用翻译API---主打个人标准版
  9. 全球-专线香港-大陆快速包税清关
  10. 程序员的“九阳神功”——设计模式