继承是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. 【Docker】安装及部署

    一.Ubuntu使用apt安装Docker 官方安装文档:https://docs.docker.com/engine/install/ubuntu/ 1.准备安装环境 [root@Docker-Ub ...

  2. ADG无法切换:报错 ORA-16467

    现象: ADG无法切换:验证时就报错 ORA-16467 记录问题,顺便展现一次troubleshooting的心路历程. 具体查询: 在主库操作, @primary 切换验证: alter data ...

  3. 2022-08-21:以下go语言代码输出什么?A:0;B:panic;C:不知道。 package main var n = -99 func main() { m := make(map[

    2022-08-21:以下go语言代码输出什么?A:0:B:panic:C:不知道. package main var n = -99 func main() { m := make(map[stri ...

  4. flask目录结构

  5. Linux 大页内存 Huge Pages 虚拟内存

    Linux为什么要有大页内存?为什么DPDK要求必须要设置大页内存?这都是由系统架构决定的,系统架构发展到现在,又是在原来的基础上一点点演变的.一开始为了解决一个问题,大家设计了一个很好的方案,随着事 ...

  6. odoo开发教程四:onchange、唯一性约束

    一:onchange机制[onchange=前端js函数!可以实现前端实时更新以及修改验证] onchange机制:不需要保存数据到数据库就可以实时更新用户界面上的显示. @api.onchange( ...

  7. Gitlab Registries

    在项目开发和部署过程中,我们常常需要一套私有仓库,比如 Code Repository.Package Repository,Docker Registry 等. Code Repository:在 ...

  8. 没用,随便写的(Dec_8_2022)

    import numpy as np from PIL import Image import pandas as pd import matplotlib.pyplot as plt # 第一个 # ...

  9. 好的,以下是我为您拟定的自然语言处理(NLP)领域的100篇热门博客文章标题,以逻辑清晰、结构紧凑、简单易懂的

    目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 4. 应用示例与代码实现讲解 1. 机器翻译 2. 文本分类 3. 情感分析 5. 优化与改进 6. 结论与展望 好的,以下是我为您拟定的 ...

  10. 计算机网络那些事之 MTU 篇

    哈喽大家好,我是咸鱼 今天我们来聊聊计算机网络中的 MTU (Maximum Transmission Unit) 什么是 MTU ? MTU(Maximum Transmission Unit)是指 ...