继承是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 = personvar 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 = personvar 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'}// 形式一:setPrototypeOffunction 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.createvar 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高级,还有很多须要开发者把握的中央,能够看看我写的其余博文,继续更新中~