关于javascript:js实现继承的五种方法及原型的继承关系

7次阅读

共计 4564 个字符,预计需要花费 12 分钟才能阅读完成。

继承是 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 高级,还有很多须要开发者把握的中央,能够看看我写的其余博文,继续更新中~

正文完
 0