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 ...
随机推荐
- 【Linux】文件及用户组合权限管理
第二周1.显示/etc目录下,以非字母开头,后面跟了一个字母以及其它任意长度任意字符的文件或目录 ls -a /etc/[^[:alpha:]][:alpha:]* 2.复制/etc目录下所有以p开头 ...
- 2020-09-01:mysql里什么是检查点、保存点和中间点?
福哥答案2020-09-01: 检查点checkpoint:批量刷盘.在一定程度上代表了刷到磁盘时日志所处的日志序列号(LSN)位置.标记重做日志中已经完成刷到磁盘的位置点,如果缓冲池中有很多重做日志 ...
- 2021-01-28:redis使用过程中的注意事项有哪些?
福哥答案2021-01-28: [答案1:](https://italk.mashibing.com/question/detail/ques_00005101)1.使用key值前缀来作命名空间虽然说 ...
- 2021-10-28:打家劫舍 II。你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装
2021-10-28:打家劫舍 II.你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金.这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的.同时,相邻的房屋装 ...
- JVM 优化踩坑记
本文记录了服务 JVM 优化的过程与思路,有对 JVM GC 原理以及对问题排查方向和工具的介绍,也有走弯路和踩坑,分享出来希望对大家有所帮助. 本文概要 服务异常和排查过程 RPC 接口超时的排查方 ...
- centOS 7 添加删除用户和用户组
1.添加新用户 由于日常使用时root用户权限过大,所以添加一个用户供日常使用,或者供他人使用. 1 新增用户 adduser [用户名] [root@centos ~]# adduser dex 2 ...
- Apache Hudi 1.x 版本重磅功能展望与讨论
Apache Hudi 社区正在对Apache Hudi 1.x版本功能进行讨论,欢迎感兴趣同学参与讨论,PR链接:https://github.com/apache/hudi/pull/8679/f ...
- 资源高效搜索方法,你 Get 到了吗?
随手转发给好友和朋友圈 编辑:办公小通 百度搜索谁都会,但是搜出来的资料往往良莠不齐.搜索速度是快了,但是还要花大量的时间去筛选. 通过下面的两个最常用的栗子,小通强烈推荐大家用高级搜索. 高级 ...
- 【理论积累】Python中的Pandas库【一】
Pandas库介绍 Pandas 是一个用于数据分析的 Python 第三方库,能够处理和分析不同格式的数据,例如:CSV.Excel.SQL 数据库等.Pandas 提供了两种数据结构,分别为 Se ...
- 使用TensorFlow进行自动化测试与部署
目录 标题:<使用 TensorFlow 进行自动化测试与部署> 背景介绍: 随着人工智能和机器学习技术的快速发展,TensorFlow 成为了一个广泛应用的深度学习框架,被广泛用于构建神 ...