js中有哪些继承方式?

PHP中文网
PHP中文网 原创
2017-06-21 09:57:37 813浏览

1 ES6中的继承

ES6使用class关键字定义类,使用extends关键字继承类。子类的constructor构造方法中必须调用super方法来获得父类的”this“对象,调用super时可以向父构造函数传参。子类可以通过super对象直接使用父类的属性和方法,也可以通过同名属性或方法覆盖父类中的定义。

class Father {
  constructor () {
    this.surname = '王'
    this.money = Infinity
  }
  sayName () {
    console.log(`My surname is ${this.surname}.`)
  }
}

class Son extends Father {
  constructor (firstname) {
    super()
    this.firstname = firstname
  }
  sayName () {
    console.log(`My name is ${super.surname}${this.firstname}.`)
  }
  sayMoney () {
    console.log(`I have ${this.money} money.`)
  }
}

let Sephirex = new Son('撕葱')
Sephirex.sayName()
Sephirex.sayMoney()

ES6中的类和继承本质上是使用prototype实现的语法糖,类中定义的方法相当于在prototype上定义方法,constructor方法中定义属性相当于构造函数模式,super方法相当于在子类中调用父类的构造函数。下面继续讨论ES5中继承的实现。

2 原型链继承

原型链继承的基本模式,就是让子类型的原型对象指向父类型的一个实例,然后再为其原型扩展方法。

function Person (name) {
  this.name = name
  this.likes = ['apple', 'orange']
}
Person.prototype.sayName = function () {
  console.log(this.name)
}

function Worker () {
  this.job = 'worker'
}
Worker.prototype = new Person()
Worker.prototype.sayJob = function () {
  console.log(this.job)
}

let Tom = new Worker()
let Jerry = new Worker()
Tom.likes.push('grape')
console.log(Jerry.likes) // [ 'apple', 'orange', 'purple' ]

原理:之前的文章里我们讨论了__proto__和prototype。子类的实例中有一个__proto__指针,指向其构造函数的原型对象。而子类构造函数的原型指向父类的一个实例,父类实例中的__proto__又指向了父类构造函数的原型......如此层层递进,就构成了原型链。
需要注意的是,即使父类中引用类型的属性是在构造函数中定义的,还是会被子类实例共享。这是因为子类构造函数的原型实际上是父类的一个实例,于是父类的实例属性自然就变成子类的原型属性,而引用类型值的原型属性会在实例之间共享。
原型链的另一个问题是,没有办法在不影响所有对象实例的情况下,给父类的构造函数传递参数。像上面的例子,用 Worker.prototype = new Person() 将子类原型指向父类实例的时候, 如果传入了初始化参数,则所有子类的实例name属性都会是传入的参数。如果这里不传参数,后边也没有的办法为父类构造函数传参了。因此很少单独使用原型链继承模式。

3 借用构造函数

借用构造函数可以解决引用类型属性被共享的问题。所谓“借用”构造函数,就是在子类构造函数中调用父类的构造函数---别忘了函数中 this 的指向跟函数在哪里定义无关,而只跟在哪里调用有关。我们可以利用call或者apply,在子类实例上调用父类的构造函数,以获取父类的属性和方法,类似ES6子类构造函数中调用super方法。

function Person (name) {
  this.name = name
  this.likes = ['apple', 'orange']
}

function Worker (name) {
  Person.call(this, name)
  this.job = 'worker'
}

let Tom = new Worker('Tom')
Tom.likes.push("grape")

let Jerry = new Worker('Jerry')

console.log(Tom.likes) // [ 'apple', 'orange', 'grape' ]
console.log(Jerry.likes) // [ 'apple', 'orange' ]

单纯使用构造函数的问题在于函数无法复用,并且子类无法获取父类prototype上的属性和方法。

4 组合继承

组合继承借用构造函数定义实例属性,使用原型链共享方法。组合继承将原型链模式和借用构造函数结合起来,从而发挥二者之长,弥补各自不足,是js中最常用的继承模式。

function Person (name) {
  this.name = name
  this.likes = ['apple', 'orange']
}
Person.prototype.sayName = function () {
  console.log(this.name)
}

function Worker (name, job) {
  Person.call(this, name) // 第二次调用 Person()
  this.job = job
}
Worker.prototype = new Person() // 第一次调用 Person()
Worker.prototype.constructor = Worker
Worker.prototype.sayJob = function () {
  console.log(this.job)
}

let Tom = new Worker('Tom', 'electrician')
Tom.likes.push('grape')
console.log(Tom.likes) // [ 'apple', 'orange', 'grape' ]
Tom.sayName() // Tom
Tom.sayJob() // electrician

let Jerry = new Worker('Jerry', 'woodworker')
console.log(Jerry.likes) // [ 'apple', 'orange' ]
Jerry.sayName() // Jerry
Jerry.sayJob() // woodworker

组合继承也并非没有缺点,那就是继承过程会两次调用父类构造函数。在第一次调用 Person 构造函数时,Worker.prototype 会得到两个属性:name 和 likes;当调用 Worker 构造函数时,又会调用一次Person 构造函数,这一次直接创建了实例属性 name 和 likes ,覆盖了原型中的两个同名属性。

5 原型式继承

如下的object函数是道格拉斯·克罗克福德在一篇文章中记录的。在 object函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲, object() 对传入其中的对象执行了一次浅复制。这种继承方式,相当于把父类型的属性和方法复制一份给子类型,然后再为子类型添加各自的属性和方法。
这种方式同样会共享引用类型值的属性。

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

let Superhero = {
  name: 'Avenger',
  skills: [],
  sayName: function () {
    console.log(this.name)
  }
}

let IronMan = object(Superhero)
IronMan.name = 'Tony Stark'
IronMan.skills.push('fly')

let CaptainAmerica = object(Superhero)
CaptainAmerica.name = 'Steve Rogers'
CaptainAmerica.skills.push('shield')

IronMan.sayName() // Tony Stark
console.log(IronMan.skills) // [ 'fly', 'shield' ]

ES5中用 Object.create() 方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create() 与 object() 方法的行为相同。Object.create() 方法的第二个参数与 Object.defineProperties() 方法的第二个参数格式相同。

let CaptainAmerica = Object.create(Superhero, {
  name: {
    value: 'Steve Rogers',
    configurable: false
  }
})

6 寄生式继承

寄生式继承很好理解,仅仅是一个封装了继承过程的工厂函数。由于方法直接定义在对象上,寄生式继承添加的方法不能复用。

function inherit(parent){
  var clone = Object.create(parent)
  clone.name = 'hulk'
  clone.sayHi = function(){
    console.log("hi")
  }
  return clone
}

let Hulk = inherit(Superhero)

Hulk.sayName() // hulk
Hulk.sayHi() // hi

7 寄生组合式继承

前面提到组合继承是js中最常用的继承方式,但不足是会调用两次父类的构造函数。寄生组合式继承可以解决这个问题,并且被认为是包含引用类型值的对象最理想的继承方式。
寄生组合式继承的基本思路是,不必为了指定子类的原型而调用父类的构造函数,需要的仅仅是父类原型的一个副本而已。寄生组合式继承就是通过借用构造函数来继承属性,然后使用寄生式继承来继承父类的原型。

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

function Person (name) {
  this.name = name
  this.likes = ['apple', 'orange']
}
Person.prototype.sayName = function () {
  console.log(this.name)
}

function Worker (name, job) {
  Person.call(this, name)
  this.job = job
}
inheritPrototype(Worker, Person)

Worker.prototype.sayJob = function () {
  console.log(this.job)
}

以上就是js中有哪些继承方式?的详细内容,更多请关注php中文网其它相关文章!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。