前言
继承是JavaScript面向对象编程非常重要的一个特性,基本在日常使用以及面试过程中都会使用到。查阅一些资料后整理以下几种继承的方法记录在自己的博客中。
如何实现继承
原型链继承
原型链继承是最简单的一种继承方式,只需要将子类的
prototype
值等于父类的实例即可
function Animal(){
this.superType = 'Animal'
}
Animal.prototype.getSuperType = function(){
console.log(this.superType);
}
function Cat(name){
this.name = name;
this.type = 'Cat';
}
// 原型继承
Cat.prototype = new Animal();
var cat = new Cat()
cat.getSuperType(); // 控制台输出Animal
以上代码是将Animal
的实例覆盖Cat
的原型,本质是重写原型对象,代之一个新类型的实例。使Cat
拥有Animal
的实例的所有属性和方法(getSuperType
为原型方法),并且还有个指针指向了Animal
的原型。当创建Cat
的实例cat
时,cat
指向的是Cat
的原型,Cat
的原型又指向Animal
的原型。
缺点
- 引用类型值的原型属性会被共享
- 在创建子类型的实例时,无法向超类型的构造函数传递参数
构造函数继承
借用构造函数继承也是非常简单地一种继承方式,即在子类构造函数的内部调用父类型构造函数。代码实现为:
function Animal(name){
this.name = name;
}
Animal.prototype.getName = function(){
console.log(this.name);
}
function Cat(){
Animal.call(this, "cat");
this.eat = "fish"
}
var instance = new Cat();
console.log(instance.name);
console.log(instance.eat); // 控制台相继输出 cat fish
instance.getName() // error getName undefined
以上代码中的 Animal
只接受一个参数 name
,该参数会直接赋给一个属性。在 Cat
构造函数内部调用 Animal
构造函数时,实际上视为 Cat
的实例设置了 name
属性。为了确保 Animal
构造函数不会重写子类的属性,可以在调用父类构造函数后,再添加应该在子类型中定义的属性。
优点
- 引用类型的原型属性不会被共享
- 可以在子类型构造函数中向父类构造函数传递参数
缺点
- 方法都在构造函数中定义,函数无法复用
- 在父类的原型定义的方法,对子类型是不可见,导致所有的类型都只能使用构造函数
组合继承
组合继承,也叫做伪经典继承,指的是将原型链和借用构造函数组合到一块
function SuperType(name) {
this.name = name
this.colors = ["red", "blue", "green"]
}
SuperType.prototype.getName = function() {
console.log(this.name)
}
function SubType(name, age) {
// 继承属性
SuperType.call(this, name)
this.age = age
}
// 继承方法
SubType.prototype = new SuperType()
SubType.prototype.sayAge = function () {
console.log(this.age)
}
let instance1 = new SubType("tom", "8")
instance1.colors.push("black")
console.log(instance1.colors) // red, blue, green, black
instance1.getName() // tom
instance1.sayAge() // 8
let instance2 = new SubType("jerry", "9")
console.log(instance2.colors) // red, blue, green
instance2.getName() // jerry
instance2.sayAge() // 9
此例子中, SuperType
构造函数定义了两个属性: name
和 colors
。 SuperType
定义了一个方法 sayName()
。 SubType
构造函数在调用 SuperType
构造函数时传入 name
参数,紧接着又定义了它自己的属性 age
。然后将 SuperType
的实例赋值给 SubType
的原型,然后又在该新原型上定义了方法 sayAge
。这样就可以让两个不同的 SubType
实例即拥有自己的属性,又可以使用相同的方法。
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript
中最常用的继承模式。而且,instanceof
和 isPortotypeOf()
也能够用于识别组合继承创建的对象。
缺点
- 会调用两次超类构造函数,并且会分别在实例和原型上有重复的属性
原型式继承
原型式继承:其思想是借助原型,可以基于已有的对象创建新的对象,同时还不用创建自定义类。以下代码,在
object()
函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()
对传入其中的对象执行了一次浅复制。
function object(o) {
function F(){}
F.prototype = o
return new F()
}
var person = {
name: 'Tom',
friends: ['Jerry', 'Sherry', 'Harry']
}
var anotherPerson = object(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')
var yetAnotherPerson = object(person)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Bob')
console.log(person.friends) // Jerry, Sherry, Harry, Rob, Bob
还可以使用 ES5
新增的 Object.create()
方法进行创建
var anotherPerson = Object.create(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')
var yetAnotherPerson = Object.create(person)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Bob')
console.log(person.friends) // Jerry, Sherry, Harry, Rob, Bob
如果只是想让一个对象与另一个对象保持类似的情况下,原型式继承是可以完全胜任的
寄生式继承
寄生式继承的思路与构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象
function createAnother(original) {
var clone = Object.create(original)
clone.sayHi = function () {
console.log('Hi')
}
return clone
}
上述代码中, createAnother()
函数接收一个参数,也就是将要作为新对象基础的对象。然后,把 original
传递给 object
函数,将返回的结果赋值给clone。再为 clone
对象添加一个新方法 sayHi()
,最后返回 clone
对象。
var person = {
name: 'Tom',
friends: ['Jerry', 'Sherry', 'Harry']
}
var anotherPerson = createAnother(person)
anotherPerson.sayHi() // Hi
在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。
寄生组合式继承
寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为指定子类行的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。代码如下:
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType (name, age) {
SuperType.call(this, name)
this.age = age
}
inheritPrototype(subType, SuperType)
SubType.prototype.sayAge = function () {
console.log(this.age)
}
该代码只调用了一次 SuperType
构造函数,并且因此避免了在 SubType.prototype
上创建不必要的,多余的属性。与此同时,原型链还能保持不变。属于最理想的继承范式。
总结
继承是 JS
中极为重要的一块知识,目前只能参考资料将这些记录下来,完全吃透还需要慢慢实战。