准确地说,JS 中没有 类 的概念,有的只是 构造函数 和 原型链。不过,我们可以通过构造函数和原型链来实现类似于类的继承,并且可以玩出很多花样、实现不同的特性。
# 原型链继承
直接把要继承的构造函数的 prototype
指向一个实例化的父类对象即可。
function Animal(name) { | |
this.name = name; | |
} | |
Animal.prototype.speak = function () { | |
console.log(this.name + " makes a noise."); | |
}; | |
function Dog(name, breed) { | |
this.breed = breed; | |
} | |
Dog.prototype = new Animal(); // 实现继承 | |
let dog = new Dog("Rex", "Bulldog"); | |
dog.speak(); // Output: Rex makes a noise. |
优点:简单易懂,符合原型链的基本思想
缺点:
- 如果弗雷包含引用类型的属性(如数组),子类实例对这个属性的修改影响其他子类实例, 因为是以引用的方式继承的 。
- 在创建子类实例时,不能向父类构造函数传递参数。
# 构造函数继承
直接调用父类构造函数的 Function.prototype.call
方法,指定 this
和参数即可。
function Animal(name) { | |
this.name = name; | |
} | |
function Dog(name, breed) { | |
Animal.call(this, name); | |
this.breed = breed; | |
} | |
let dog = new Dog("Rex", "Bulldog"); |
优点:
- 避免了引用类型的属性共享问题,传入的值直接在这个实例中单独初始化。
- 可以在创建子类实例时向父类传递参数。
缺点:
- 方法都在构造函数中定义,无法直接的实现函数复用,每个实例都有自己独立的方法副本。
# 组合继承(原型链继承 + 构造函数继承)
在子类构造函数内部先用 call
绑定 this
,然后再指定子类构造函数的 prototype
为父类的实例,最后把 prototype.constructor
指向自己。
function Animal(name) { | |
this.name = name; | |
} | |
Animal.prototype.speak = function () { | |
console.log(this.name + " makes a noise."); | |
}; | |
function Dog(name, breed) { | |
Animal.call(this, name); | |
this.breed = breed; | |
} | |
Dog.prototype = new Animal(); | |
Dog.prototype.constructor = Dog; | |
let dog = new Dog("Rex", "Bulldog"); |
优点:结合了原型链继承和构造函数继承的优点,避免了它们各自的缺点。
缺点:调用了 两次 父类构造函数,导致子类原型上 多了不需要的父类属性 。
# 原型式继承
使用 Object.create
方法来创建一个子类对象。
let animal = { | |
type: "animal", | |
speak: function () { | |
console.log(this.name + " makes a noise."); | |
}, | |
}; | |
let dog = Object.create(animal); | |
dog.name = "Rex"; |
优点:不需要定义构造函数,只需要指定要继承的对象,就可以创建对象。
缺点:和原型链继承一样,引用类型的属性会被共享。
# 寄生式继承
提供了一个返回子类实例的函数,在其内部调用 Object.create
方法创建子类实例,并为其指定相关属性,之后将其返回。
function createDog(name) { | |
let dog = Object.create(animal); | |
dog.name = name; | |
return dog; | |
} |
优点:可以在不必为新对象构建自定义类型的情况下,实现对象的属性继承。
缺点:和原型链继承一样,引用类型的属性会被共享。
# 寄生组合式继承
引入一个函数 inheritPrototype
,传入子类构造函数和父类构造函数,将两者的 prototype
绑定到一起(类似于 new
操作符)。
其余的部分和构造函数继承类似。
function inheritPrototype(subType, superType) { | |
let prototype = Object.create(superType.prototype); | |
prototype.constructor = subType; | |
subType.prototype = prototype; | |
} | |
function Animal(name) { | |
this.name = name; | |
} | |
Animal.prototype.speak = function () { | |
console.log(this.name + " makes a noise."); | |
}; | |
function Dog(name, breed) { | |
Animal.call(this, name); | |
this.breed = breed; | |
} | |
inheritPrototype(Dog, Animal); | |
let dog = new Dog("Rex", "Bulldog"); | |
dog.speak(); |
优点:解决了组合继承中调用两次父类构造函数的问题,避免了不必要的属性初始化。
缺点:相对于其他方式,寄生组合式继承是比较完善的一种继承方式,但是在方法实现上相对比较复杂。