继承是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__)

执行结果如下

继承关系如下

  1. 实例对象

    • 定义obj字面量相当于创建Function Object的实例,所以obj的隐式原型等于Object的显式原型
    • foo对象是构造函数Foo的实例,所以foo的隐式等于Foo的显式原型
  2. 函数对象

    • 函数Foo也是对象,是构造函数Function的实例,所以函数Foo的隐式原型等于Function的显式原型
    • 函数Function也是对象,是构造函数Function的实例,所以函数Function的隐式原型等于Function的显式原型,即Function的隐式原型与显式原型相等
    • 函数Object也是对象,是构造函数Function的实例,所以函数Object的隐式原型等于Function的显式原型
  3. 原型对象

    • Foo的显式原型也是对象,是构造函数Object的实例,所以Foo的显式原型对象的隐式原型等于Object的显式原型
    • Function的显式原型也是对象,是构造函数Object的实例,所以Function的显式原型对象的隐式原型等于Object的显式原型
    • Object的显式原型也是对象,它的隐式原型指向null

图示如下

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

以上就是js实现继承的五种方法及原型的继承关系,关于js高级,还有很多需要开发者掌握的地方,可以看看我写的其他博文,持续更新中~

js实现继承的五种方法及原型的继承关系的更多相关文章

  1. js对象之间的"继承"的五种方法

    今天要介绍的是,对象之间的"继承"的五种方法. 比如,现在有一个"动物"对象的构造函数. function Animal(){ this.species = & ...

  2. 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; ...

  3. HTML用JS导出Excel的五种方法

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  4. js一种继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法。

    js一种继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法. function ClassA(sColor) { this.color = sColor; } ClassA ...

  5. java:JavaScript2:(setTimeout定时器,history.go()前进/后退,navigator.userAgent判断浏览器,location.href,五种方法获取标签属性,setAttribute,innerHTML,三种方法获取form表单信息,JS表单验证,DOM对象,form表单操作)

    1.open,setTimeout,setInterval,clearInterval,clearTimeout <!DOCTYPE> <html> <head> ...

  6. js去掉字符串前后空格的五种方法

    转载 :http://www.2cto.com/kf/201204/125943.html 第一种:循环检查替换[javascript]//供使用者调用  function trim(s){  ret ...

  7. JS学习笔记——JavaScript继承的6种方法(原型链、借用构造函数、组合、原型式、寄生式、寄生组合式)

    JavaScript继承的6种方法 1,原型链继承 2,借用构造函数继承 3,组合继承(原型+借用构造) 4,原型式继承 5,寄生式继承 6,寄生组合式继承 1.原型链继承. <script t ...

  8. js去掉字符串前后空格的五种方法(转)

    出处:http://www.2cto.com/kf/201204/125943.html 第一种:循环检查替换[javascript]//供使用者调用  function trim(s){  retu ...

  9. Java 字符串拼接 五种方法的性能比较分析 从执行100次到90万次

    [请尊重原创版权,如需引用,请注明来源及地址] > 字符串拼接一般使用“+”,但是“+”不能满足大批量数据的处理,Java中有以下五种方法处理字符串拼接,各有优缺点,程序开发应选择合适的方法实现 ...

  10. js最好的继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法。

    js最好的继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法. function ClassA(sColor) { this.color = sColor; } Class ...

随机推荐

  1. 消息推送平台的实时数仓?!flink消费kafka消息入到hive

    大家好,3y啊.好些天没更新了,并没有偷懒,只不过一直在安装环境,差点都想放弃了. 上一次比较大的更新是做了austin的预览地址,把企业微信的应用和机器人消息各种的消息类型和功能给完善了.上一篇文章 ...

  2. 2023-03-27:avio_list_dir.c 是 FFmpeg 库自带的一个示例程序,它提供了列出目录中所有文件和子目录的功能,请用go语言改写。

    2023-03-27:avio_list_dir.c 是 FFmpeg 库自带的一个示例程序,它提供了列出目录中所有文件和子目录的功能,请用go语言改写. 答案2023-03-27: 这段代码实现了通 ...

  3. 利用简单的IO操作实现M3U8文件之间的合并

    先上代码: 1 @SneakyThrows //合并操作,最终文件不包含结束标识,方便多次合并 2 private static void mergeM3U8File(String source, S ...

  4. ADG级联备库环境PSU应用验证

    上篇文章 源端为备库的场景下Duplicate失败问题 我只在中间备库环境应用了PSU,解决了级联备库从中间备库duplicate数据库的问题: 细心的朋友已经发现,因为是备库环境,并没有做数据库执行 ...

  5. # 代码随想录算法训练营Day28 回溯算法|93.复原IP地址 78.子集 90.子集II

    代码随想录算法训练营 93.复原IP地址 题目链接:93.复原IP地址 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 有效的 IP 地址 正好由四个整数(每个整数位于 0 到 ...

  6. django购物车的实现

    1 购物车的实现问题思路 购物车需求分析: 1 未登陆和已登陆都保存到用户的购物车数据. 2 用户可以对购物车进行增删改查: 3 购物车有选择状态,只有选中的状态才能生成订单: 4 用户登陆时,合并c ...

  7. 3. docker的实践玩法

    1. docker的进程架构 docker服务进程:就是针对docker服务的命令,启动,重启 接口:通过参数指定容器的IP和端口,实现对容器的远程操作 客户端命令行:对docker的操作命令 最后学 ...

  8. 【python基础】类-初识类

    1.面向对象思想 在认识类之前,我们需要理解面向对象思想和面向过程思想. 面向过程思想:要拥有一间房屋,面向过程像是自己来修盖房屋,如果需要经过选址.购买材料.砌墙.装修等步骤,面向过程编程,就相当于 ...

  9. 前端Vue图片上传组件支持单个文件多个文件上传 自定义上传数量 预览删除图片 图片压缩

    前端Vue图片上传组件支持单个文件多个文件上传 自定义上传数量 预览删除图片 图片压缩, 下载完整代码请访问uni-app插件市场址:https://ext.dcloud.net.cn/plugin? ...

  10. TheTransformerPlaybookforNLPandLanguageUnderstanding

    目录 2.1 基本概念解释 2.2 技术原理介绍 2.3 相关技术比较 3. 实现步骤与流程 3.1 准备工作:环境配置与依赖安装 3.1.1 准备工作:环境配置与依赖安装 3.1.2 核心模块实现 ...