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 ...
随机推荐
- 2022-11-28:给定两个数组A和B,比如 A = { 0, 1, 1 } B = { 1, 2, 3 } A[0] = 0, B[0] = 1,表示0到1有双向道路 A[1] = 1, B[1]
2022-11-28:给定两个数组A和B,比如 A = { 0, 1, 1 } B = { 1, 2, 3 } A[0] = 0, B[0] = 1,表示0到1有双向道路 A[1] = 1, B[1] ...
- 2022-09-22:以下go语言代码输出什么?A:5、B:不能编译;C:运行时死锁。 package main import ( “fmt“ “time“ ) func main
2022-09-22:以下go语言代码输出什么?A:5.B:不能编译:C:运行时死锁. package main import ( "fmt" "time" ) ...
- 2021-05-23:给定一个字符串str,str表示一个公式,公式里可能有整数、加减乘除符号和左右括号。返回公式的计算结果,难点在于括号可能嵌套很多层。str=“48*((70-65)-43)+8*
2021-05-23:给定一个字符串str,str表示一个公式,公式里可能有整数.加减乘除符号和左右括号.返回公式的计算结果,难点在于括号可能嵌套很多层.str="48*((70-65)-4 ...
- 在asp.net core web api中添加efcore使用codefirst
首先创建webapi项目,我这里使用的版本是.net6 在nuget中添加对应的工具包 红框标出来的是对应的数据库扩展包,mysql用mysql版,sqlserver用sqlserver版,选择正确的 ...
- 基于ggplot2的解剖图和组织模块可视化
摘要 将数据显示到解剖结构上,是一种可以快速观察组织相关信息的便捷技术.然而,绘制组织是一项复杂的任务(a complex task),需要解剖学和艺术方面的专业知识.虽然已经存在可用于在解剖图上显示 ...
- shell工具和脚本
Shell脚本 shell 脚本是一种更加复杂度的工具. 大多数shell都有自己的一套脚本语言,包括变量.控制流和自己的语法.shell脚本 与其他脚本语言不同之处在于,shell 脚本针对 she ...
- 解决element-ui下拉框数据过多,导致页面卡顿问题与本地分页功能实现
效果 前情提要: 最近使用element-ui开发的一个页面,在打开的时候占用cpu非常高,有时候都能达到90%↑.在调试时发现其中一个下拉框的接口返回2k↑的数据.本着有问题问百度的精神,看到主要的 ...
- 翻译:REST 和 gRPC 详细比较
译者注:在微服务架构设计,构建API和服务间通信技术选型时,对 REST 和 gRPC 的理解和应用还存在知识盲区,近期看到国外的这篇文章:A detailed comparison of REST ...
- 【技术积累】JavaSciprt中的函数【一】
什么是函数?如何声明函数? JavaScript中的函数是一段可重复使用的代码块,它可以接受输入并返回输出. 在JavaScript中,函数是一种特殊的对象,因此可以将其存储在变量中,将其作为参数传递 ...
- 将onnx的静态batch改为动态batch及修改输入输出层的名称
目录 背景 操作 修改输入输出层 修改输入输出层名称 完整代码 背景 在模型的部署中,为了高效利用硬件算力,常常会需要将多个输入组成一个batch同时输入网络进行推理,这个batch的大小根据系统的负 ...