JavaScript - 继承的几种实现方式

#原型链继承

基于对象的 原型链 来实现继承是JavaScript的核心机制。JavaScript中的继承是通过 原型链 实现的:当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的 原型对象 里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是 原型链 的概念。原型链的尽头一般来说都是 Object.prototype

原型链继承
JavaScript
function Weather() {
this.condition = 'Sunny'
}
Weather.prototype.getCondition = function () {
return this.condition
}
function Forecast() {}
Forecast.prototype = new Weather() // 继承自Weather
Forecast.prototype.constructor = Forecast
const forecast = new Forecast()
console.log(forecast.getCondition()) // "Sunny"
缺点:
  • 包含 引用类型 的原型属性会被所有实例属性共享,容易造成属性的修改混乱
  • 在创建子类型的实例时,不能向父类构造函数中传递参数

基于以上问题,在实践中很少会单独使用原型链。

#构造函数继承

构造函数继承的思想主要是在子类构造函数中调用父类构造函数,使用 callapply 方法来改变函数执行时的上下文。

构造函数继承
JavaScript
function Weather(condition) {
this.condition = condition
}
function Forecast(condition, temperature) {
Weather.call(this, condition) // 继承自Weather
this.temperature = temperature
}
const forecast = new Forecast('Cloudy', 22)
console.log(forecast.condition) // "Cloudy"
console.log(forecast.temperature) // 22
优点:
  • 可以在子类型构造函数中向父类构造函数添加参数
  • 解决了原型链继承中 引用类型 属性共享的问题
缺点:
  • 只能继承父类的 实例属性 和方法,不能继承 原型链 上的属性和方法
  • 每次创建子类实例都会执行父类的构造函数,性能开销较大

基于以上问题,构造函数继承的技术也是很少单独使用的。

#组合式继承

组合继承(也称为 伪经典继承 )结合了原型链继承和构造函数继承的优点。

组合继承
JavaScript
function Weather(condition) {
this.condition = condition
}
Weather.prototype.getCondition = function () {
return this.condition
}
function Forecast(condition, temperature) {
Weather.call(this, condition) // 继承实例属性
this.temperature = temperature
}
Forecast.prototype = new Weather() // 继承原型属性
Forecast.prototype.constructor = Forecast
const forecast = new Forecast('Rainy', 18)
console.log(forecast.getCondition()) // "Rainy"
console.log(forecast.temperature) // 18
优点:
  • 组合继承结合了原型链继承和构造函数继承的优点,既可以继承父类的 实例属性 (包括 引用类型 的属性),也可以继承父类的 原型方法
  • 通过构造函数调用,子类的实例属性不会共享父类的 引用类型 属性,从而避免了原型链继承中可能引发的属性共享问题
缺点:
  • 调用了两次父类的构造函数,导致基类的 原型对象 中增添了不必要的父类的实例对象中的所有属性

#寄生式组合继承

寄生式继承 的思路是创建一个仅用于封装继承过程的函数,该函数在内部以某种方式增强对象,最后返回这个对象。这种方式是目前最理想的JavaScript继承范式。

寄生式组合继承
JavaScript
function Weather(condition) {
this.condition = condition
}
Weather.prototype.getCondition = function () {
return this.condition
}
function Forecast(condition, temperature) {
Weather.call(this, condition) // 继承实例属性
this.temperature = temperature
}
// 使用Object.create方法创建一个新对象,使用现有的对象来作为新创建对象的原型
Forecast.prototype = Object.create(Weather.prototype)
Forecast.prototype.constructor = Forecast
const forecast = new Forecast('Snowy', -5)
console.log(forecast.getCondition()) // "Snowy"
console.log(forecast.temperature) // -5
优点:
  • 解决了组合继承的冗余调用问题,性能更优
  • 继承了父类的 实例属性原型属性
  • 被认为是引用类型最理想的继承范式
缺点:
  • 使用该继承方式,在为对象添加函数的时候,没有办法做到函数的复用,实现稍微复杂一些

#Class 继承

ES6 引入了 class 关键字,提供了更直观的语法糖来实现继承。这种语法更加清晰易懂,但本质上仍然是基于原型的继承。

ES6 Class继承
JavaScript
class Weather {
constructor(condition) {
this.condition = condition
}
getCondition() {
return this.condition
}
}
class Forecast extends Weather {
constructor(condition, temperature) {
super(condition) // 继承实例属性
this.temperature = temperature
}
}
const forecast = new Forecast('Windy', 15)
console.log(forecast.getCondition()) // "Windy"
console.log(forecast.temperature) // 15
优点:
  • 语法更加简洁和直观
  • 支持继承 静态方法
  • 更好的结构化代码,符合 面向对象编程 的习惯
缺点:
  • 只是 语法糖 ,本质上仍然基于 原型链继承