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 相关文章推荐
脚本收藏iframe
Jul 21 Javascript
用JavaScript脚本实现Web页面信息交互
Oct 11 Javascript
菜单效果
Oct 14 Javascript
SWFObject Flash js调用类
Jul 08 Javascript
javascript当中的代码嗅探扩展原生对象和原型(prototype)
Jan 11 Javascript
判断日期是否能跨月查询的js代码
Jul 25 Javascript
jQuery中:radio选择器用法实例
Jan 03 Javascript
Javascript中拼接大量字符串的方法
Feb 05 Javascript
jquery 标签 隔若干行加空白或者加虚线的方法
Dec 07 Javascript
JS树形菜单组件Bootstrap TreeView使用方法详解
Dec 21 Javascript
Javascript中类式继承和原型式继承的实现方法和区别之处
Apr 25 Javascript
详解vue2.0监听属性的使用心得及搭配计算属性的使用
Jul 18 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 模拟 asp.net webFrom 按钮提交事件实例
2014/10/13 PHP
php结合正则获取字符串中数字
2015/06/19 PHP
php关闭warning问题的解决方法
2016/05/17 PHP
PHP使用PDO调用mssql存储过程的方法示例
2017/10/07 PHP
实例讲解PHP中使用命名空间
2019/01/27 PHP
Jquery常用技巧收集整理篇
2010/11/14 Javascript
Bootstrap所支持的表单控件实例详解
2016/05/16 Javascript
完美的js div拖拽实例代码
2016/09/24 Javascript
javascript实现一个网页加载进度loading
2017/01/04 Javascript
vue基于Element构建自定义树的示例代码
2017/09/19 Javascript
Nodejs+angularjs结合multiparty实现多图片上传的示例代码
2017/09/29 NodeJs
详解开源的JavaScript插件化框架MinimaJS
2017/10/26 Javascript
使用typescript开发angular模块并发布npm包
2018/04/19 Javascript
vue中各选项及钩子函数执行顺序详解
2018/08/25 Javascript
JavaScript迭代器的含义及用法
2019/06/21 Javascript
JavaScript中var的重要性实例分析
2019/07/09 Javascript
如何使用 JavaScript 操作浏览器历史记录 API
2020/11/24 Javascript
原生js+canvas实现验证码
2020/11/29 Javascript
[49:15]DOTA2-DPC中国联赛 正赛 CDEC vs XG BO3 第二场 1月19日
2021/03/11 DOTA
PowerBI和Python关于数据分析的对比
2019/07/11 Python
wxpython自定义下拉列表框过程图解
2020/02/14 Python
解决python脚本中error: unrecognized arguments: True错误
2020/04/20 Python
Python日志处理模块logging用法解析
2020/05/19 Python
python sleep和wait对比总结
2021/02/03 Python
CSS3之transition实现下划线的示例代码
2018/05/30 HTML / CSS
HTML5 video 上传预览图片视频如何设置、预览视频某秒的海报帧
2018/08/28 HTML / CSS
什么是ESB?请介绍一下ESB?
2015/05/27 面试题
五年级数学教学反思
2014/02/11 职场文书
爱护公物标语
2014/06/24 职场文书
借名购房协议书范本
2014/10/06 职场文书
2014年小学教研工作总结
2014/12/06 职场文书
社会主义核心价值观主题教育活动总结
2015/05/07 职场文书
2016年暑期教师培训心得体会
2016/01/09 职场文书
详解Redis复制原理
2021/06/04 Redis
SQL Server中使用判断语句(IF ELSE/CASE WHEN )案例
2021/07/07 SQL Server
Web应用开发TypeScript使用详解
2022/05/25 Javascript