JavaScript 六种继承方式


title: JS的六种继承方式
date: 2017-06-27 05:55:49
tags: JS
categories: 学习
---javascript

继承是面向对象编程中又一很是重要的概念,JavaScript支持实现继承,不支持接口继承,实现继承主要依靠原型链来实现的。java

1、原型链

首先得要明白什么是原型,原型链继承基本思想就是让一个原型对象指向另外一个的实例编程

function SuperType() {
  this.property = true
}
SuperType.prototype.getSuperValue = function () {
  return this.property
}
function SubType() {
  this.subproperty = false
}
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function () {
  return this.subproperty
}
var instance = new SubType()
console.log(instance.getSuperValue()) // true

代码定义了两个类型SuperType和SubType,每一个类型分别有一个属性和一个方法,SubType继承了SuperType,而继承是经过建立SuperType的实例,并将该实例赋给SubType.prototype实现的。数组

实现的本质是重写原型对象,代之以一个新类型的实例,那么存在SuperType的实例中的全部属性和方法,如今也存在于SubType.prototype中了。app

咱们知道,在建立一个实例的时候,实例对象中会有一个内部指针指向建立它的原型,进行关联起来,在这里代码SubType.prototype = new SuperType(),也会在SubType.prototype建立一个内部指针,将SubType.prototype与SuperType关联起来。函数

因此instance指向SubType的原型,SubType的原型又指向SuperType的原型,继而在instance在调用getSuperValue()方法的时候,会顺着这条链一直往上找。学习

添加方法

在给SubType原型添加方法的时候,若是,父类上也有一样的名字,SubType将会覆盖这个方法,达到重写的目的。 可是这个方法依然存在于父类中。this

记住不能以字面量的形式添加,由于,上面说过经过实例继承本质上就是重写,再使用字面量形式,又是一次重写了,但此次重写没有跟父类有任何关联,因此就会致使原型链截断。prototype

function SuperType() {
  this.property = true
}
SuperType.prototype.getSuperValue = function () {
  return this.property
}
function SubType() {
  this.subproperty = false
}
SubType.prototype = new SuperType()
SubType.prototype = {
  getSubValue:function () {
   return this.subproperty
  }
}
var instance = new SubType()
console.log(instance.getSuperValue())  // error

问题

单纯的使用原型链继承,主要问题来自包含引用类型值的原型。指针

function SuperType() {
  this.colors = ['red', 'blue', 'green']
}
function SubType() {
}
SubType.prototype = new SuperType()
var instance1 = new SubType()
var instance2 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors)  // ["red", "blue", "green", "black"]
console.log(instance2.colors) // ["red", "blue", "green", "black"]

在SuperType构造函数定义了一个colors属性,当SubType经过原型链继承后,这个属性就会出现SubType.prototype中,就跟专门建立了SubType.prototype.colors同样,因此会致使SubType的全部实例都会共享这个属性,因此instance1修改colors这个引用类型值,也会反映到instance2中。

2、借用构造函数

此方法为了解决原型中包含引用类型值所带来的问题。

这种方法的思想就是在子类构造函数的内部调用父类构造函数,能够借助apply()和call()方法来改变对象的执行上下文

function SuperType() {
  this.colors = ['red', 'blue', 'green']
}
function SubType() {
  // 继承SuperType
  SuperType.call(this)
}
var instance1 = new SubType()
var instance2 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors)  // ["red", "blue", "green", "black"]
console.log(instance2.colors) // ["red", "blue", "green"]

在新建SubType实例是调用了SuperType构造函数,这样以来,就会在新SubType对象上执行SuperType函数中定义的全部对象初始化代码。

结果,SubType的每一个实例就会具备本身的colors属性的副本了。

传递参数

借助构造函数还有一个优点就是能够传递参数

function SuperType(name) {
  this.name = name
}
function SubType() {
  // 继承SuperType
  SuperType.call(this, 'Jiang')
 
  this.job = 'student'
}
var instance = new SubType()
console.log(instance.name)  // Jiang
console.log(instance.job)   // student

问题

若是仅仅借助构造函数,方法都在构造函数中定义,所以函数没法达到复用

3、组合继承(原型链+构造函数)

组合继承是将原型链继承和构造函数结合起来,从而发挥两者之长的一种模式。

思路就是使用原型链实现对原型属性和方法的继承,而经过借用构造函数来实现对实例属性的继承。

这样,既经过在原型上定义方法实现了函数复用,又可以保证每一个实例都有它本身的属性。

function SuperType(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
  console.log(this.name)
}
function SubType(name, job) {
  // 继承属性
  SuperType.call(this, name)
  this.job = job
}
// 继承方法
SubType.prototype = new SuperType()
SubType.prototype.constructor = SuperType
SubType.prototype.sayJob = function() {
  console.log(this.job)
}
var instance1 = new SubType('Jiang', 'student')
instance1.colors.push('black')
console.log(instance1.colors) //["red", "blue", "green", "black"]
instance1.sayName() // 'Jiang'
instance1.sayJob()  // 'student'
var instance2 = new SubType('J', 'doctor')
console.log(instance2.colors) // //["red", "blue", "green"]
instance2.sayName()  // 'J'
instance2.sayJob()  // 'doctor'

这种模式避免了原型链和构造函数继承的缺陷,融合了他们的优势,是最经常使用的一种继承模式。
constructor始终指向建立当前对象的构造函数。

4、原型式继承

借助原型能够基于已有的对象建立新对象,同时还没必要所以建立自定义类型。

function object(o) {
  function F() {}
  F.prototype = o
  return new F()
}

在object函数内部,先建立一个临时性的构造函数,而后将传入的对象做为这个构造函数的原型,最后返回这个临时类型的一个新实例。

本质上来讲,object对传入其中的对象执行了一次浅复制。

var person = {
  name: 'Jiang',
  friends: ['Shelby', 'Court']
}
var anotherPerson = object(person)
console.log(anotherPerson.friends)  // ['Shelby', 'Court']

这种模式要去你必须有一个对象做为另外一个对象的基础。

在这个例子中,person做为另外一个对象的基础,把person传入object中,该函数就会返回一个新的对象。

这个新对象将person做为原型,因此它的原型中就包含一个基本类型和一个引用类型。

因此意味着若是还有另一个对象关联了person,anotherPerson修改数组friends的时候,也会体如今这个对象中。

Object.create()方法

ES5经过Object.create()方法规范了原型式继承,能够接受两个参数,一个是用做新对象原型的对象和一个可选的为新对象定义额外属性的对象,行为相同,基本用法和上面的object同样,除了object不能接受第二个参数之外。

var person = {
  name: 'Jiang',
  friends: ['Shelby', 'Court']
}
var anotherPerson = Object.create(person)
console.log(anotherPerson.friends)  // ['Shelby', 'Court']

5、寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式相似,即建立一个仅用于封装继承过程的函数。

function createAnother(o) {
  var clone = Object.create(o) // 建立一个新对象
  clone.sayHi = function() { // 添加方法
    console.log('hi')
  }
  return clone  // 返回这个对象
}
var person = {
  name: 'Jiang'
}
var anotherPeson = createAnother(person)
anotherPeson.sayHi() // hi

基于person返回了一个新对象anotherPeson,新对象不只拥有了person的属性和方法,还有本身的sayHi方法。

在主要考虑对象而不是自定义类型和构造函数的状况下,这是一个有用的模式。

6、寄生组合式继承

在前面说的组合模式(原型链+构造函数)中,继承的时候须要调用两次父类构造函数。

父类

function SuperType(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}

第一次在子类构造函数中

function SubType(name, job) {
  // 继承属性
  SuperType.call(this, name)
 
  this.job = job
}

第二次将子类的原型指向父类的实例

// 继承方法
SubType.prototype = new SuperType()

当使用var instance = new SubType()的时候,会产生两组name和color属性,一组在SubType实例上,一组在SubType原型上,只不过实例上的屏蔽了原型上的。

使用寄生式组合模式,能够规避这个问题。

这种模式经过借用构造函数来继承属性,经过原型链的混成形式来继承方法。

基本思路:没必要为了指定子类型的原型而调用父类的构造函数,咱们须要的无非就是父类原型的一个副本。

本质上就是使用寄生式继承来继承父类的原型,在将结果指定给子类型的原型。

function inheritPrototype(subType, superType) {
  var prototype = Object.create(superType.prototype)
  prototype.constructor = subType
  subType.prototype = prototype
}

该函数实现了寄生组合继承的最简单形式。

这个函数接受两个参数,一个子类,一个父类。

第一步建立父类原型的副本,第二步将建立的副本添加constructor属性,第三部将子类的原型指向这个副本。

function SuperType(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
  console.log(this.name)
}
function SubType(name, job) {
  // 继承属性
  SuperType.call(this, name)
 
  this.job = job
}

function inheritPrototype(subType, superType) {
  var prototype = Object.create(superType.prototype)
  prototype.constructor = subType
  subType.prototype = prototype
}
// 继承
inheritPrototype(SubType, SuperType)
var instance = new SubType('Jiang', 'student')
instance.sayName()

补充:直接使用Object.create来实现,其实就是将上面封装的函数拆开,这样演示能够更容易理解。

function SuperType(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
  console.log(this.name)
}
function SubType(name, job) {
  // 继承属性
  SuperType.call(this, name)
 
  this.job = job
}
// 继承
SubType.prototype = Object.create(SuperType.prototype)
// 修复constructor
SubType.prototype.constructor = SubType
var instance = new SubType('Jiang', 'student')
instance.sayName()

ES6新增了一个方法,Object.setPrototypeOf,能够直接建立关联,并且不用手动添加constructor属性。

// 继承
Object.setPrototypeOf(SubType.prototype, SuperType.prototype)
console.log(SubType.prototype.constructor === SubType) // true

欢迎访问个人博客

相关文章
相关标签/搜索