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中有哪些继承方式?的详细内容。