详述JavaScript实现继承的几种方式(推荐)


Posted in Javascript onMarch 22, 2016

ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的。

原型链

原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的指针。如果:我们让原型对象A等于另一个类型B的实例,那么原型对象A就会有一个指针指向B的原型对象,相应的B的原型对象中保存着指向其构造函数的指针。假如B的原型对象又是另一个类型的实例,那么上述的关系依旧成立,如此层层递进,就构成了实例与原型的链条。

详述JavaScript实现继承的几种方式(推荐) 

实例以及构造函数和原型之间的关系图如下所示:

详述JavaScript实现继承的几种方式(推荐) 

person.constructor现在指向的是Parent,这是因为Child.prototype指向了Parent的原型,而Parent原型对象的constructor指向Parent。

当以读取模式访问一个实例属性时,首先会在实例中搜索该属性,如果没有找到该属性,则会继续搜索实例的原型。在通过原型链实现的集成中,搜索过程就会沿着原型链继续向上,直到搜索到原型链的末端。

例如,调用person.getParentValue()方法,1)搜索实例;2)搜索Child.prototype;3)搜索Parent.prototype;找到了getParentValue()方法停止。

1、默认的原型

前面的例子中展示的原型链少了一环,所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。因此默认的原型都包含一个内部指针,指向Object.prototype,这也正是所有自定义类型会继承toString()、ValueOf()等默认方法的根本原因。换句话说Object.prototype就是原型链的末端。

详述JavaScript实现继承的几种方式(推荐) 

2、确定原型和实例的关系

通过两种方式可以确定原型和实例之间的关系,第一种是使用instanceOf操作符,第二种是使用isPrototypeOf()方法。
实例 instanceOf 原型链 中出现过的构造函数,都会返回true

console.log(person instanceOf Child);//true 

console.log(person instanceOf Parent);//true 
console.log(person instanceOf Object);//true 
isPrototype(),只要是原型链中出现过的原型,都可以说是该原型链所派生出来的实例的原型,因此也返回true. 
console.log(Object.prototype.isPrototypeOf(instance));//true 
console.log(Parent.prototype.isPrototypeOf(instance));//true 
console.log(Child.prototype.isPrototypeOf(instance));//true

3、谨慎地定义方法

子类型有时候需要覆盖超类型中的某个方法,或者需要添加超类型中不存在的莫个方法,注意:给原型添加方法的代码一定要放在替换原型的语句之后。

当通过Child的实例调用getParentValue()时,调用的是这个重新定义过的方法,但是通过Parent的实例调用getParentValue()时,调用的还是原来的方法。

格外需要注意的是:必须要在Parent的实例替换原型之后,再定义这两个方法。

还有一点需要特别注意的是:通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样做会重写原型链。

详述JavaScript实现继承的几种方式(推荐) 

以上代码刚把Parent的实例赋值给Child的原型对象,紧接着又将原型替换成一个字面量,替换成字面量之后,Child原型实际上包含的是一个Object的实例,而不再是Parent的实例,因此我们设想中的原型链被切断.Parent和Child之间没有任何关联。

4、原型链的问题

原型链很强大,可以利用它来实现继承,但是也有一些问题,主要的问题还是包含引用类型值的原型属性会被所有实例共享。因此我们在构造函数中定义实例属性。但是在通过原型来实现继承时,原型对象其实变成了另一个类型的实例。于是原先定义在构造函数中的实例属性变成了原型属性了。

举例说明如下:

详述JavaScript实现继承的几种方式(推荐) 

在Parent构造函数中定义了一个friends属性,该属性值是一个数组(引用类型值)。这样,Parent的每个实例都会各自包含自己的friends属性。当Child通过原型链继承了Parent之后,Child.prototype也用用了friends属性——这就好像friends属性是定义在Child.prototype一样。这样Child的所有实例都会共享这个friends属性,因此我们对kid1.friends做的修改,在kid2.friends中也会体现出来,显然,这不是我们想要的。

原型链的另一个问题是:在创建子类型的实例时,不能在不影响所有对象实例的情况下,给超类型的构造函数传递参数。因此,我们通常很少会单独使用原型链。

借用构造函数

为了解决原型中包含引用类型值带来的一些问题,引入了借用构造函数的技术。这种技术的基础思想是:在子类型构造函数的内部调用超类型构造函数。

详述JavaScript实现继承的几种方式(推荐) 

Parent.call(this)在新创建的Child实例的环境下调用了Parent构造函数。在新创建的Child实例环境下调用Parent构造函数。这样,就在新的Child对象上,此处的kid1和kid2对象上执行Parent()函数中定义的对象初始化代码。这样,每个Child实例就都会具有自己的friends属性的副本了。

借用构造函数的方式可以在子类型的构造函数中向超类型构造函数传递参数。

详述JavaScript实现继承的几种方式(推荐) 

为了确保子类型的熟悉不会被父类的构造函数重写,可以在调用父类构造函数之后,再添加子类型的属性。
构造函数的问题:

构造函数模式的问题,在于方法都在构造函数中定义,函数复用无从谈起,因此,借用构造函数的模式也很少单独使用。

组合继承

组合继承指的是将原型链和借用构造函数的技术组合在一块,从而发挥二者之长。即:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

详述JavaScript实现继承的几种方式(推荐) 

Person构造函数定义了两个属性:name和friends。Person的原型定义了一个方法sayName()。Child构造函数在调用Parent构造函数时,传入了name参数,紧接着又定义了自己的属性age。然后将Person的实例赋值给Child的原型,然后又在该原型上定义了方法sayAge().这样,两个不同的Child实例既分别拥有自己的属性,包括引用类型的属性,又可以使用相同的方法了。
组合继承避免了原型链和构造函数的缺陷,融合了他们的有点,成为JavaScript中最常用的继承模式。而且,instanceOf和isPropertyOf()也能够识别基于组合继承创建的对象。

最后,关于JS对象和继承都还有几种模式没有写,或者说,我自己也还未去深刻研究,但是,我想,首先将组合模式应用的游刃有余。并且,对于为何选用组合模式,知其然,知其所以然。

关于JavaScript实现继承的几种方式(推荐),小编就给大家介绍到这里,希望对大家有所帮助!

Javascript 相关文章推荐
仅用[]()+!等符号就足以实现几乎任意Javascript代码
Mar 01 Javascript
使用jQuery UI的tooltip函数修饰title属性的气泡悬浮框
Jun 24 Javascript
javascript断点调试心得分享
Apr 23 Javascript
webpack入门+react环境配置
Feb 08 Javascript
js实现图片旋转 js滚动鼠标中间对图片放大缩小
Jul 05 Javascript
jQuery常用选择器详解
Jul 17 jQuery
vue+iview 实现可编辑表格的示例代码
Oct 31 Javascript
微信小程序HTTP接口请求封装代码实例
Sep 05 Javascript
layui 富文本图片上传接口与普通按钮 文件上传接口的例子
Sep 23 Javascript
Vue3不支持Filters过滤器的问题
Sep 24 Javascript
JavaScript 中判断变量是否为数字的示例代码
Oct 22 Javascript
深入了解Vue3模板编译原理
Nov 19 Vue.js
jQuery toggle 代替方法
Mar 22 #Javascript
一款简单的jQuery图片标注效果附源码下载
Mar 22 #Javascript
酷炫jQuery全屏3D焦点图动画效果
Mar 22 #Javascript
浅析C/C++,Java,PHP,JavaScript,Json数组、对象赋值时最后一个元素后面是否可以带逗号
Mar 22 #Javascript
使用struts2+Ajax+jquery验证用户名是否已被注册
Mar 22 #Javascript
使用getBoundingClientRect方法实现简洁的sticky组件的方法
Mar 22 #Javascript
Node.js文件操作方法汇总
Mar 22 #Javascript
You might like
PHP类中的魔术方法(Magic Method)简明总结
2014/07/08 PHP
php设计模式之简单工厂模式详解
2014/09/04 PHP
php变量与JS变量实现不通过跳转直接交互的方法
2017/08/25 PHP
PHP读取并输出XML文件数据的简单实现方法
2017/12/22 PHP
详解PHP版本兼容之openssl调用参数
2018/07/25 PHP
通过JS来判断页面控件是否获取焦点
2014/01/03 Javascript
基于jquery实现的图片在各种分辨率下未知的容器内上下左右居中
2014/05/11 Javascript
js 加密压缩出现bug解决方案
2014/11/25 Javascript
jQuery实现选项卡切换效果简单演示
2015/12/09 Javascript
JavaScript判断DIV内容是否为空的方法
2016/01/29 Javascript
简介EasyUI datagrid editor combogrid搜索框的实现
2016/04/01 Javascript
Jquery组件easyUi实现手风琴(折叠面板)示例
2016/08/23 Javascript
JS用斜率判断鼠标进入DIV四个方向的方法
2016/11/07 Javascript
js实现微博发布小功能
2017/01/12 Javascript
微信小程序开发之animation循环动画实现的让云朵飘效果
2017/07/14 Javascript
jQuery基于cookie实现换肤功能实例
2017/10/14 jQuery
浅谈AngularJS中$http服务的简单用法
2018/05/15 Javascript
angularjs下ng-repeat点击元素改变样式的实现方法
2018/09/12 Javascript
基于JS实现table导出Excel并保留样式
2020/05/19 Javascript
vue实现可移动的悬浮按钮
2021/03/04 Vue.js
Python多线程编程(七):使用Condition实现复杂同步
2015/04/05 Python
Python获取邮件地址的方法
2015/07/10 Python
Python首次安装后运行报错(0xc000007b)的解决方法
2016/10/18 Python
Python3使用turtle绘制超立方体图形示例
2018/06/19 Python
python里的单引号和双引号的有什么作用
2020/06/17 Python
利用Python中的Xpath实现一个在线汇率转换器
2020/09/09 Python
苹果中国官方网站:Apple中国
2016/07/22 全球购物
英国体育器材进口商店:UK Sport Imports
2017/03/14 全球购物
美国领先的水果篮送货公司和新鲜水果供应商:The Fruit Company
2018/02/13 全球购物
C语言笔试题回忆
2015/04/02 面试题
教育英语专业毕业生的求职信
2014/03/13 职场文书
副检察长四风问题对照检查材料思想汇报
2014/10/07 职场文书
党员自评材料范文
2014/12/17 职场文书
政工师工作总结2015
2015/05/26 职场文书
使用react+redux实现计数器功能及遇到问题
2021/06/02 Javascript
Springboot配置suffix指定mvc视图的后缀方法
2021/07/03 Java/Android