欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  web前端

js中有哪些继承方式?

程序员文章站 2022-04-19 19:00:59
...

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中有哪些继承方式?的详细内容,更多请关注其它相关文章!

相关标签: javascript 继承