js中有哪些继承方式?
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学习系列5