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 ...
随机推荐
- Nuxt3.0中使用EChart可视化图表📊
在Nuxt3.0项目中用到了可视化图表,于是我用了EChart可视化图表库.但是在官网我没有找到针对在Nuxt3.0中使用EChart的方法,于是在这里记录我的引入EChart并简单使用的步骤.需要声 ...
- 2023-03-15:屏幕录制并且显示视频,不要用命令。代码用go语言编写。
2023-03-15:屏幕录制并且显示视频,不要用命令.代码用go语言编写. 答案2023-03-15: 使用moonfdd/ffmpeg-go和moonfdd/sdl2-go库来实现屏幕录制并显示视 ...
- 2022-11-18:给定一个数组arr,表示连续n天的股价,数组下标表示第几天 指标X:任意两天的股价之和 - 此两天间隔的天数 比如 第3天,价格是10 第9天,价格是30 那么第3天和第9天的指
2022-11-18:给定一个数组arr,表示连续n天的股价,数组下标表示第几天 指标X:任意两天的股价之和 - 此两天间隔的天数 比如 第3天,价格是10 第9天,价格是30 那么第3天和第9天的指 ...
- 2022-09-03:n块石头放置在二维平面中的一些整数坐标点上 每个坐标点上最多只能有一块石头 如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。 给你一个长度为 n 的数组
2022-09-03:n块石头放置在二维平面中的一些整数坐标点上 每个坐标点上最多只能有一块石头 如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头. 给你一个长度为 n 的数组 ...
- 2022-07-20:以下go语言代码是关于json 和 context的,输出什么?A:{};B:{“a“:“b“};C:{“Context“:0};D:不确定。 package main imp
2022-07-20:以下go语言代码是关于json 和 context的,输出什么?A:{}:B:{"a":"b"}:C:{"Context&quo ...
- Linux 创建 Python 虚拟环境
Linux 创建 Python 虚拟环境 0. 前言 网上教程太杂太乱,要么排版不好看,要么讲半天讲不到重点,故做此篇,精简干练. 1. 安装virtualenv 先用pip安装virtualenv第 ...
- Python爬取微信小程序实战(通用)
背景介绍 最近遇到一个需求,大致就是要获取某个小程序上的数据.心想小程序本质上就是移动端加壳的浏览器,所以想到用Python去获取数据.在网上学习了一下如何实现后,记录一下我的实现过程以及所踩过的小坑 ...
- 用批处理干掉汇编masm link六回车
干掉麻烦的六回车 踏破铁鞋无觅处,得来全不费功夫 前言 最近在上汇编语言的课,在手动编译链接的时候你可能做过以下操作: 输入 masm 1.asm 回车 回车 回车 输入 link 1.obj 回车 ...
- 【python基础】新建/运行python项目
1.新建python项目 在编写程序之前,我们需要新建一个项目(Project),在桌面双击PyCharm的快捷方式,等待片刻,打开如下所示的软件界面.点击New Project 在弹出的对话框中,需 ...
- vue iview 单击table行变色 获取行数据
用到两个事件: 代码如下: 1 <Table :columns="columns" :data="tableData" @on-row-click=&qu ...