js实现继承的五种方法及原型的继承关系
继承是javascript中实现代码复用的一种方式,也能绑定对象或者函数之间的关系
为什么要继承
比如以下代码,Person、Student和Teacher构造函数,可以发现他们有一些特征
- Person和Student都有姓名、年龄的属性和吃的方法,但Student还有学号、分数的属性和学习的方法
- Person和Teacher都有姓名、年龄的属性和吃的方法,但Teacher还有教学的方法
function Person(name, age) {
this.name = name
this.age = age
this.eating = function () {
console.log('eating')
}
}
function Student(name, age, sno, score) {
this.name = name
this.age = age
this.sno = sno
this.score = score
this.eating = function () {
console.log('eating')
}
this.studing = function () {
console.log('studing')
}
}
function Teacher(name, age) {
this.name = name
this.age = age
this.eating = function () {
console.log('eating')
}
this.teaching = function () {
console.log('teaching')
}
}
可以发现在定义函数的时候有很多的重复代码,而且Person和Student、Teacher是包含关系,继承就是通过这种包含关系为突破口来减少的重复代码
父类原型赋值给子类
这种方式是直接把子类和父类原型指向同一个对象
Student.prototype = Person.prototype
这样会将所有添加到子元素原型上的属性和方法都添加到父元素身上,再增加一个子元素,也会拥有其它子元素的所有方法,这样实现继承的方式并不推荐
原型式继承
通过函数的prototype属性就可以实现继承关系,原型式继承就是将Student.prototype赋值为Person构造函数实例对象
图解如下

function Person() {
this.name = 'alice'
}
Person.prototype.eating = function () {
console.log('eating')
}
function Student(score) {
this.score = score
}
var person = new Person()
Student.prototype = person
var student = new Student(88)
console.log(student)
console.log(student.name)
console.log(student.__proto__)
student.eating()
以上代码的执行结果为

这样Student的实例对象可以访问Person的属性和方法,但是存在一些问题
- 通过打印,无法获取Student继承的Person属性和方法
- 不能自定义Person中属性的值
借用构造函数继承
基于以上问题,在原型式继承的基础上来借用构造函数来解决,子函数内通过call/apply来调用父函数
function Person(name) {
this.name = name
}
Person.prototype.eating = function () {
console.log('eating')
}
function Student(name, score) {
Person.call(this, name)
this.score = score
}
var person = new Person()
Student.prototype = person
var student = new Student('kiki', 88)
console.log(student)
console.log(student.name)
console.log(student.__proto__)
student.eating()
执行结果如下

通过这样的方式,可以解决原型式继承存在的两个问题,一是可以从Student的实例对象上找到父元素Person的属性和方法,二是可以自定义Person属性的值。
但这样一种定义方式也存在问题
- Person构造函数至少被执行了两次,一次是创建Person的实例对象,一次是Student构造函数中通过call调用
- 创建的person对象也保存了一份Person的数据,但这份数据是不必要的
寄生式继承
寄生式继承结合了原型式继承和工厂函数,创建一个实现继承的函数,在函数内部操作对象并返回
var user = {
flying: function(){
console.log('flying')
}
}
function createObj(name, age){
var obj = Object.create(user)
obj.name = name
obj.age = age
obj.eating = function(){
console.log('eating')
}
return obj
}
var kiki = createObj('kiki', 18)
console.log(kiki)
console.log(kiki.__proto__)
执行结果如下

通过寄生式继承,解决了借用构造函数重复调用父类函数和保存不必要数据的问题,但它又产生了新的问题
- 无法知道对象的类型,比如Person或者Student
- 每个对象都保存了一份eating的方法,其实是没有必要的
寄生组合式继承
通过一个对象来链接父子函数之间的继承关系
图示如下

首先定义一个原型继承函数,提供三种定义的方式,可以根据项目兼容性选择
var obj = {
name: 'alice'
}
// 方式一:setPrototypeOf
function createObject1(o) {
var obj = {}
Object.setPrototypeOf(obj, o)
return obj
}
// 方式二:构造函数
function createObject2(o) {
function Fn() { }
Fn.prototype = o
var obj = new Fn()
return obj
}
var obj1 = createObject1(obj)
var obj2 = createObject2(obj)
// 方式三:Object.create
var obj3 = Object.create(obj)
console.log(Object.getOwnPropertyDescriptors(obj1.__proto__))
console.log(Object.getOwnPropertyDescriptors(obj2.__proto__))
console.log(Object.getOwnPropertyDescriptors(obj3.__proto__))
执行结果为

然后再将原型式继承函数添加到构造函数的继承关系中
function createObj(o) {
function Fn() { }
Fn.prototype = o
return new Fn()
}
// 封装继承的函数
function inheritPrototype(subType, superType){
subType.prototype = createObj(superType.prototype)
Object.defineProperty(subType.prototype, 'constructor', {
value: subType,
configurable: true
})
}
function Person(name, age) {
this.name = name
this.age = age
this.eating = function () {
console.log('eating')
}
}
function Student(name, age, score) {
Person.call(this, name, age)
this.score = score
}
inheritPrototype(Student, Person)
Student.prototype.running = function () {
console.log('running')
}
var student = new Student('alice', 18, 100)
console.log(student)
student.running()
执行结果如下

寄生组合式继承就能比较好的解决以上继承方式存在的问题
原型的继承关系
实例对象、构造函数、原型对象之间存在继承的关系
var obj = {}
function Foo() { }
var foo = new Foo()
console.log(obj.__proto__ === Object.prototype)
console.log(foo.__proto__ === Foo.prototype)
console.log(Foo.__proto__ === Function.prototype)
console.log(Function.__proto__ === Function.prototype)
console.log(Object.__proto__ === Function.prototype)
console.log(Foo.prototype.__proto__ === Object.prototype)
console.log(Function.prototype.__proto__ === Object.prototype)
console.log(Object.prototype.__proto__)
执行结果如下

继承关系如下
实例对象
- 定义obj字面量相当于创建Function Object的实例,所以obj的隐式原型等于Object的显式原型
- foo对象是构造函数Foo的实例,所以foo的隐式等于Foo的显式原型
函数对象
- 函数Foo也是对象,是构造函数Function的实例,所以函数Foo的隐式原型等于Function的显式原型
- 函数Function也是对象,是构造函数Function的实例,所以函数Function的隐式原型等于Function的显式原型,即Function的隐式原型与显式原型相等
- 函数Object也是对象,是构造函数Function的实例,所以函数Object的隐式原型等于Function的显式原型
原型对象
- Foo的显式原型也是对象,是构造函数Object的实例,所以Foo的显式原型对象的隐式原型等于Object的显式原型
- Function的显式原型也是对象,是构造函数Object的实例,所以Function的显式原型对象的隐式原型等于Object的显式原型
- Object的显式原型也是对象,它的隐式原型指向null
图示如下

上述原型关系更为详细的图解如下

以上就是js实现继承的五种方法及原型的继承关系,关于js高级,还有很多需要开发者掌握的地方,可以看看我写的其他博文,持续更新中~
js实现继承的五种方法及原型的继承关系的更多相关文章
- js对象之间的"继承"的五种方法
今天要介绍的是,对象之间的"继承"的五种方法. 比如,现在有一个"动物"对象的构造函数. function Animal(){ this.species = & ...
- html table表格导出excel的方法 html5 table导出Excel HTML用JS导出Excel的五种方法 html中table导出Excel 前端开发 将table内容导出到excel HTML table导出到Excel中的解决办法 js实现table导出Excel,保留table样式
先上代码 <script type="text/javascript" language="javascript"> var idTmr; ...
- HTML用JS导出Excel的五种方法
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...
- js一种继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法。
js一种继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法. function ClassA(sColor) { this.color = sColor; } ClassA ...
- java:JavaScript2:(setTimeout定时器,history.go()前进/后退,navigator.userAgent判断浏览器,location.href,五种方法获取标签属性,setAttribute,innerHTML,三种方法获取form表单信息,JS表单验证,DOM对象,form表单操作)
1.open,setTimeout,setInterval,clearInterval,clearTimeout <!DOCTYPE> <html> <head> ...
- js去掉字符串前后空格的五种方法
转载 :http://www.2cto.com/kf/201204/125943.html 第一种:循环检查替换[javascript]//供使用者调用 function trim(s){ ret ...
- JS学习笔记——JavaScript继承的6种方法(原型链、借用构造函数、组合、原型式、寄生式、寄生组合式)
JavaScript继承的6种方法 1,原型链继承 2,借用构造函数继承 3,组合继承(原型+借用构造) 4,原型式继承 5,寄生式继承 6,寄生组合式继承 1.原型链继承. <script t ...
- js去掉字符串前后空格的五种方法(转)
出处:http://www.2cto.com/kf/201204/125943.html 第一种:循环检查替换[javascript]//供使用者调用 function trim(s){ retu ...
- Java 字符串拼接 五种方法的性能比较分析 从执行100次到90万次
[请尊重原创版权,如需引用,请注明来源及地址] > 字符串拼接一般使用“+”,但是“+”不能满足大批量数据的处理,Java中有以下五种方法处理字符串拼接,各有优缺点,程序开发应选择合适的方法实现 ...
- js最好的继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法。
js最好的继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法. function ClassA(sColor) { this.color = sColor; } Class ...
随机推荐
- Weblogic11g安装部署-winserver篇
目录 一.安装weblogic11g 1.1找到下载好的weblogic11g 1.2打开安装程序wls1033_oepe111150_win32.exe,并完成初始化如下图 1.3点击下一步并选择安 ...
- Windows server 2012 r2 激活方法
slmgr /ipk W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9 slmgr /skms kms.03k.org slmgr /ato
- vue一直处在2.9.6版本卸载不了升级不了解决方案
直接 win+R打开cmd窗口运行命令: 输入where vue,找到vue安装位置,把找到的文件删掉 再执行一次npm uninstall vue-cli -g, vue -V,已经没有版本号了 此 ...
- for循环原理补充、生成器对象、yield冷门用法、生成器表达式的面试题、常见内置函数
目录 一.for循环原理补充 二.生成器对象 (1).自定义生成器对标range功能(一个参数 两个参数 三个参数 迭代器对象) 三.yield冷门用法 (1).yield与return的对比 四.生 ...
- 树莓派上使用docker部署aria2,minidlna
目前在树莓派上安装aria2跟minidlna能搜到的教程基本上都是直接apt-get install安装的.现在是docker的时代了,其实这2个东西可以直接使用docker run跑起来.有什么问 ...
- JS加载机制
做开发多年后发现自己思维任然只定位在功能的实现:忽略了很多代码最终的实现,加载机制等 那我们接下来就借助几篇博客来理理天天看见的js:理理他究竟是怎么过执行的 一.代码检测 首先浏览器会对所有js 进 ...
- flutter系列之:做一个会飞的菜单
目录 简介 定义一个菜单项目 让menu动起来 添加菜单内部的动画 总结 简介 flutter中自带了drawer组件,可以实现通用的菜单功能,那么有没有一种可能,我们可以通过自定义动画来实现一个别样 ...
- 判断两个矩形是否相交(Rect Intersection)
0x00 Preface 最近在开发一个2D组态图形组件的过程中,里面的数学模块,涉及到两个矩形是否相交的判断. 这个问题很多年前就写过,算是个小的算法吧. 网络上搜索一下,有很多思路,有一些思路要基 ...
- 【技术积累】Python中的NumPy库【二】
NumPy库的主要类有哪些? NumPy库的主要类包括: ndarray:N维数组对象,是NumPy最重要的类之一.它是Python中数组的基本数据结构,可以进行高效的数学计算和数据处理操作. ufu ...
- 详解共识算法的Raft算法模拟数
摘要:Raft算法是一种分布式共识算法,用于解决分布式系统中的一致性问题. 本文分享自华为云社区<共识算法之Raft算法模拟数>,作者: TiAmoZhang . 01.Leader选举 ...