JavaScript的六种继承方式(推荐)


Posted in Javascript onJune 26, 2017

继承是面向对象编程中又一非常重要的概念,JavaScript支持实现继承,不支持接口继承,实现继承主要依靠原型链来实现的。

JavaScript的六种继承方式(推荐)

原型链

首先得要明白什么是原型链,在一篇文章看懂proto和prototype的关系及区别中讲得非常详细

原型链继承基本思想就是让一个原型对象指向另一个类型的实例

function SuperType() { 
 this.property = true 
} 
SuperType.prototype.getSuperValue = function () { 
 return this.property 
} 
function SubType() { 
 this.subproperty = false 
} 
SubType.prototype = new SuperType() 
SubType.prototype.getSubValue = function () { 
 return this.subproperty 
} 
var instance = new SubType() 
console.log(instance.getSuperValue()) // true

代码定义了两个类型SuperType和SubType,每个类型分别有一个属性和一个方法,SubType继承了SuperType,而继承是通过创建SuperType的实例,并将该实例赋给SubType.prototype实现的。

实现的本质是重写原型对象,代之以一个新类型的实例,那么存在SuperType的实例中的所有属性和方法,现在也存在于SubType.prototype中了。

我们知道,在创建一个实例的时候,实例对象中会有一个内部指针指向创建它的原型,进行关联起来,在这里代码SubType.prototype = new SuperType(),也会在SubType.prototype创建一个内部指针,将SubType.prototype与SuperType关联起来。

所以instance指向SubType的原型,SubType的原型又指向SuperType的原型,继而在instance在调用getSuperValue()方法的时候,会顺着这条链一直往上找。

添加方法

在给SubType原型添加方法的时候,如果,父类上也有同样的名字,SubType将会覆盖这个方法,达到重新的目的。 但是这个方法依然存在于父类中。

记住不能以字面量的形式添加,因为,上面说过通过实例继承本质上就是重写,再使用字面量形式,又是一次重写了,但这次重写没有跟父类有任何关联,所以就会导致原型链截断。

function SuperType() { 
 this.property = true 
} 
SuperType.prototype.getSuperValue = function () { 
 return this.property 
} 
function SubType() { 
 this.subproperty = false 
} 
SubType.prototype = new SuperType() 
SubType.prototype = { 
 getSubValue:function () { 
 return this.subproperty 
 } 
} 
var instance = new SubType() 
console.log(instance.getSuperValue()) // error

问题

单纯的使用原型链继承,主要问题来自包含引用类型值的原型。

function SuperType() { 
 this.colors = ['red', 'blue', 'green'] 
} 
function SubType() { 
} 
SubType.prototype = new SuperType() 
var instance1 = new SubType() 
var instance2 = new SubType() 
instance1.colors.push('black') 
console.log(instance1.colors) // ["red", "blue", "green", "black"] 
console.log(instance2.colors) // ["red", "blue", "green", "black"]

在SuperType构造函数定义了一个colors属性,当SubType通过原型链继承后,这个属性就会出现SubType.prototype中,就跟专门创建了SubType.prototype.colors一样,所以会导致SubType的所有实例都会共享这个属性,所以instance1修改colors这个引用类型值,也会反映到instance2中。

借用构造函数

此方法为了解决原型中包含引用类型值所带来的问题。

这种方法的思想就是在子类构造函数的内部调用父类构造函数,可以借助apply()和call()方法来改变对象的执行上下文

function SuperType() { 
 this.colors = ['red', 'blue', 'green'] 
} 
function SubType() { 
 // 继承SuperType 
 SuperType.call(this) 
} 
var instance1 = new SubType() 
var instance2 = new SubType() 
instance1.colors.push('black') 
console.log(instance1.colors) // ["red", "blue", "green", "black"] 
console.log(instance2.colors) // ["red", "blue", "green"]

在新建SubType实例是调用了SuperType构造函数,这样以来,就会在新SubType对象上执行SuperType函数中定义的所有对象初始化代码。

结果,SubType的每个实例就会具有自己的colors属性的副本了。

传递参数

借助构造函数还有一个优势就是可以传递参数

function SuperType(name) { 
 this.name = name 
} 
function SubType() { 
 // 继承SuperType 
 SuperType.call(this, 'Jiang') 
 this.job = 'student' 
} 
var instance = new SubType() 
console.log(instance.name) // Jiang 
console.log(instance.job) // student

问题

如果仅仅借助构造函数,方法都在构造函数中定义,因此函数无法达到复用

组合继承(原型链+构造函数)

组合继承是将原型链继承和构造函数结合起来,从而发挥二者之长的一种模式。

思路就是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

function SuperType(name) { 
 this.name = name 
 this.colors = ['red', 'blue', 'green'] 
} 
SuperType.prototype.sayName = function () { 
 console.log(this.name) 
} 
function SubType(name, job) { 
 // 继承属性 
 SuperType.call(this, name) 
 this.job = job 
} 
// 继承方法 
SubType.prototype = new SuperType() 
SubType.prototype.constructor = SuperType 
SubType.prototype.sayJob = function() { 
 console.log(this.job) 
} 
var instance1 = new SubType('Jiang', 'student') 
instance1.colors.push('black') 
console.log(instance1.colors) //["red", "blue", "green", "black"] 
instance1.sayName() // 'Jiang' 
instance1.sayJob() // 'student' 
var instance2 = new SubType('J', 'doctor') 
console.log(instance2.colors) // //["red", "blue", "green"] 
instance2.sayName() // 'J' 
instance2.sayJob() // 'doctor'

这种模式避免了原型链和构造函数继承的缺陷,融合了他们的优点,是最常用的一种继承模式。

原型式继承

借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

function object(o) { 
 function F() {} 
 F.prototype = o 
 return new F() 
}

在object函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。

本质上来说,object对传入其中的对象执行了一次浅复制。

var person = { 
 name: 'Jiang', 
 friends: ['Shelby', 'Court'] 
} 
var anotherPerson = object(person) 
console.log(anotherPerson.friends) // ['Shelby', 'Court']

这种模式要去你必须有一个对象作为另一个对象的基础。

在这个例子中,person作为另一个对象的基础,把person传入object中,该函数就会返回一个新的对象。

这个新对象将person作为原型,所以它的原型中就包含一个基本类型和一个引用类型。

所以意味着如果还有另外一个对象关联了person,anotherPerson修改数组friends的时候,也会体现在这个对象中。

Object.create()方法

ES5通过Object.create()方法规范了原型式继承,可以接受两个参数,一个是用作新对象原型的对象和一个可选的为新对象定义额外属性的对象,行为相同,基本用法和上面的object一样,除了object不能接受第二个参数以外。

var person = { 
 name: 'Jiang', 
 friends: ['Shelby', 'Court'] 
} 
var anotherPerson = Object.create(person) 
console.log(anotherPerson.friends) // ['Shelby', 'Court']

寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数。

function createAnother(o) { 
 var clone = Object.create(o) // 创建一个新对象 
 clone.sayHi = function() { // 添加方法 
 console.log('hi') 
 } 
 return clone // 返回这个对象 
} 
var person = { 
 name: 'Jiang' 
} 
var anotherPeson = createAnother(person) 
anotherPeson.sayHi()

基于person返回了一个新对象anotherPeson,新对象不仅拥有了person的属性和方法,还有自己的sayHi方法。

在主要考虑对象而不是自定义类型和构造函数的情况下,这是一个有用的模式。

寄生组合式继承

在前面说的组合模式(原型链+构造函数)中,继承的时候需要调用两次父类构造函数。

父类

function SuperType(name) { 
 this.name = name 
 this.colors = ['red', 'blue', 'green'] 
}

第一次在子类构造函数中

function SubType(name, job) { 
 // 继承属性 
 SuperType.call(this, name) 
 this.job = job 
}

第二次将子类的原型指向父类的实例

// 继承方法 
SubType.prototype = new SuperType()

当使用var instance = new SubType()的时候,会产生两组name和color属性,一组在SubType实例上,一组在SubType原型上,只不过实例上的屏蔽了原型上的。

使用寄生式组合模式,可以规避这个问题。

这种模式通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

基本思路:不必为了指定子类型的原型而调用父类的构造函数,我们需要的无非就是父类原型的一个副本。

本质上就是使用寄生式继承来继承父类的原型,在将结果指定给子类型的原型。

function inheritPrototype(subType, superType) { 
 var prototype = Object.create(superType.prototype) 
 prototype.constructor = subType 
 subType.prototype = prototype 
}

该函数实现了寄生组合继承的最简单形式。

这个函数接受两个参数,一个子类,一个父类。

第一步创建父类原型的副本,第二步将创建的副本添加constructor属性,第三部将子类的原型指向这个副本。

function SuperType(name) { 
 this.name = name 
 this.colors = ['red', 'blue', 'green'] 
} 
SuperType.prototype.sayName = function () { 
 console.log(this.name) 
} 
function SubType(name, job) { 
 // 继承属性 
 SuperType.call(this, name) 
 this.job = job 
} 
// 继承 
inheritPrototype(SubType, SuperType) 
var instance = new SubType('Jiang', 'student') 
instance.sayName()

补充:直接使用Object.create来实现,其实就是将上面封装的函数拆开,这样演示可以更容易理解。

function SuperType(name) { 
 this.name = name 
 this.colors = ['red', 'blue', 'green'] 
} 
SuperType.prototype.sayName = function () { 
 console.log(this.name) 
} 
function SubType(name, job) { 
 // 继承属性 
 SuperType.call(this, name) 
 this.job = job 
} 
// 继承 
SubType.prototype = Object.create(SuperType.prototype) 
// 修复constructor 
SubType.prototype.constructor = SubType 
var instance = new SubType('Jiang', 'student') 
instance.sayName()

ES6新增了一个方法,Object.setPrototypeOf,可以直接创建关联,而且不用手动添加constructor属性。

// 继承 
Object.setPrototypeOf(SubType.prototype, SuperType.prototype) 
console.log(SubType.prototype.constructor === SubType) // true

以上所述是小编给大家介绍的JavaScript的六种继承方式(推荐),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
javascript 出生日期和身份证判断大全
Nov 13 Javascript
JS 文件本身编码转换 图文教程
Oct 12 Javascript
js图片处理示例代码
May 12 Javascript
express的中间件bodyParser详解
Dec 04 Javascript
javascript中数组的定义及使用实例
Jan 21 Javascript
JavaScript实现在标题栏上显示当前日期的方法
Mar 19 Javascript
js判断子窗体是否关闭的方法
Aug 11 Javascript
jQuery实现图片上传和裁剪插件Croppie
Nov 29 Javascript
JavaScript的设计模式经典之建造者模式
Feb 24 Javascript
使用FileReader API创建Vue文件阅读器组件
Apr 03 Javascript
JavaScript数组、json对象、eval()函数用法实例分析
Feb 21 Javascript
vue组件中iview的modal组件爬坑问题之modal的显示与否应该是使用v-show
Apr 12 Javascript
JavaScript数据类型和变量_动力节点Java学院整理
Jun 26 #Javascript
JavaScript基本语法_动力节点Java学院整理
Jun 26 #Javascript
JavaScript条件判断_动力节点Java学院整理
Jun 26 #Javascript
JavaScript脚本语言是什么_动力节点Java学院整理
Jun 26 #Javascript
JavaScript简介_动力节点Java学院整理
Jun 26 #Javascript
JavaScript数组_动力节点Java学院整理
Jun 26 #Javascript
Angular 表单控件示例代码
Jun 26 #Javascript
You might like
php 不同编码下的字符串长度区分
2009/09/26 PHP
zend framework配置操作数据库实例分析
2012/12/06 PHP
原生JS可拖动弹窗效果实例代码
2013/11/09 Javascript
JavaScript中实现继承的三种方式和实例
2015/01/29 Javascript
JS实现的通用表单验证插件完整实例
2015/08/20 Javascript
jquery+CSS实现的多级竖向展开树形TRee菜单效果
2015/08/24 Javascript
jQuery实现鼠标经过时高亮,同时其他同级元素变暗的效果
2016/09/18 Javascript
微信小程序开发之录音机 音频播放 动画实例 (真机可用)
2016/12/08 Javascript
tablesorter.js表格排序使用方法(支持中文排序)
2017/02/10 Javascript
详解vue渲染函数render的使用
2017/12/12 Javascript
微信小程序修改swiper默认指示器样式的实例代码
2018/07/18 Javascript
JavaScript实用代码小技巧
2018/08/23 Javascript
vue+iview 实现可编辑表格的示例代码
2018/10/31 Javascript
JavaScript Math对象和调试程序的方法分析
2019/05/13 Javascript
Webpack中SplitChunksPlugin 配置参数详解
2020/03/24 Javascript
基于JavaScript实现随机点名器
2021/02/25 Javascript
Python导出DBF文件到Excel的方法
2015/07/25 Python
Python实现网站注册验证码生成类
2017/06/08 Python
Python模块文件结构代码详解
2018/02/03 Python
python实现彩色图转换成灰度图
2019/01/15 Python
Python 中list ,set,dict的大规模查找效率对比详解
2019/10/11 Python
Python基于pygame实现单机版五子棋对战
2019/12/26 Python
python requests库的使用
2021/01/06 Python
CSS3弹性伸缩布局之box布局
2016/07/12 HTML / CSS
澳大利亚首屈一指的在线购物目的地:Kogan.com
2017/02/02 全球购物
日本快乐生活方式购物网站:Shop Japan
2018/07/17 全球购物
HashMap和Hashtable的区别
2013/05/18 面试题
某科技软件测试面试题
2013/05/19 面试题
简历中求职的个人自我评价
2013/12/03 职场文书
历史专业学生的自我评价
2014/02/28 职场文书
授权委托书样本
2014/04/03 职场文书
干部四风问题整改措施思想汇报
2014/10/13 职场文书
2014年车间工作总结
2014/11/21 职场文书
五年级学生评语大全
2014/12/26 职场文书
文艺委员竞选稿
2015/11/19 职场文书
asyncio异步编程之Task对象详解
2022/03/13 Python