Skip to content

寄生组合式继承与 class 继承 ​

难度等级:🔴 高级 分类:JavaScript 归档日期:2026-10-09


请介绍 JavaScript 的寄生组合式继承和 class 继承,说明原型链、constructor、super,以及类字段与 getter/setter 的关系。

面试口述答案 ​

JavaScript 的继承主要依靠原型链:读取对象属性时,先查自身,没有就沿 [[Prototype]] 向上查找,直到 null。

寄生组合式继承分为两步:通过 Parent.call(this, ...) 执行父构造函数,为每个实例初始化独立属性;通过 Object.create(Parent.prototype) 连接子类原型与父类原型,继承共享方法。它不需要为了连接原型链而执行父构造函数。

替换子类的 prototype 后,通常还要修复 constructor。不修复不会影响实例创建、方法继承和 instanceof,但读取实例的 constructor 时可能得到父构造函数。

class 继承用 extends 自动建立继承关系,用 super() 调用父构造函数。在派生类构造函数中,必须先执行 super(),才能使用 this。super.method() 从父级原型查找方法,但执行时的 this 仍然是当前实例。

两种写法并不完全等价。extends 还会建立构造函数自身的原型链,从而继承静态成员;类方法默认不可枚举,类内部采用严格模式,类构造函数必须使用 new 调用。

属性方面,构造函数中的 this.name = value 和类字段 name = value 通常都产生实例自身属性,但前者是赋值,可能触发原型上的 setter;后者直接定义自身属性,不触发继承的 setter。类体中的普通方法和 getter/setter 则定义在原型上。

最小代码示例 ​

两段代码分别运行。

寄生组合式继承:

js
function Animal(name) {
  this.name = name
  this.favoriteFoods = [] // 每个实例独立拥有
}

Animal.prototype.introduce = function () {
  return `我叫${this.name}`
}

function Cat(name, personality) {
  Animal.call(this, name)
  this.personality = personality
}

Cat.prototype = Object.create(Animal.prototype)

Object.defineProperty(Cat.prototype, 'constructor', {
  value: Cat,
  writable: true,
  configurable: true,
  enumerable: false,
})

Cat.prototype.introduce = function () {
  return `${Animal.prototype.introduce.call(this)},性格${this.personality}`
}

const huhui = new Cat('灰灰', '安静')

class 继承:

js
class Animal {
  constructor(name) {
    this.name = name
    this.favoriteFoods = []
  }

  introduce() {
    return `我叫${this.name}`
  }
}

class Cat extends Animal {
  constructor(name, personality) {
    super(name)
    this.personality = personality
  }

  introduce() {
    return `${super.introduce()},性格${this.personality}`
  }
}

const huhui = new Cat('灰灰', '安静')

两者的实例原型链都是:

js
Object.getPrototypeOf(huhui) === Cat.prototype         // true
Object.getPrototypeOf(Cat.prototype) === Animal.prototype // true
Object.getPrototypeOf(Animal.prototype) === Object.prototype // true
Object.getPrototypeOf(Object.prototype) === null      // true

追问要点 ​

追问回答要点
prototype、[[Prototype]]、__proto__ 有什么区别?prototype 是构造函数提供给实例的原型对象;[[Prototype]] 是对象内部的原型关系;__proto__ 是访问该关系的传统访问器,通常使用 Object.getPrototypeOf() 读取
不修复 constructor 会怎样?读取时可能沿原型链得到父构造函数;不影响 new Child() 和普通的 instanceof 检查
instanceof 判断什么?通常检查右侧构造函数的 prototype 是否出现在左侧对象的原型链上
实例属性和原型属性怎么选择?独立状态放实例上,共享方法放原型上;数组、对象放原型上会共享其内容
给实例的同名属性赋值会怎样?对普通可写原型数据属性,会创建自身属性并遮蔽原型属性;访问器属性要遵循 setter 规则
super.name 能读取父构造函数创建的 name 吗?不能直接读到实例自身属性;应使用 this.name。实例方法里的 super.name 从父级原型开始查找
super 调用 getter 时,this 是谁?当前调用的实例
类字段和构造函数赋值同时存在,谁生效?基类字段先初始化,再执行构造函数体;子类字段在 super() 返回后、子类构造函数剩余代码执行前初始化
原型上只有 get name(),执行 this.name = value 会怎样?没有自身属性遮蔽时,类中的赋值会抛出 TypeError;类字段 name = value 则直接创建自身属性,遮蔽 getter

特别注意:constructor 是可修改的普通属性,不是决定继承关系的机制;super 决定父级属性的查找起点,并不代表另一个“父类实例”。

完整示例:动物 → 猫 → 美短 ​

用三层继承演示实例属性、原型上的共享默认值与方法、方法重写和调用父级实现。两段代码分别运行。

1. 寄生组合式继承 ​

js
// ===== 1. 动物 =====

function Animal(name, age) {
  this.name = name
  this.age = age
  this.isAlive = true

  // 每个实例拥有独立数组
  this.favoriteFoods = []
}

// 原型上的共享默认值
Animal.prototype.kingdom = '动物界'

// 原型上的共享方法
Animal.prototype.introduce = function () {
  return `我叫${this.name},今年${this.age}岁。`
}

Animal.prototype.addFavoriteFood = function (food) {
  this.favoriteFoods.push(food)
}

Animal.prototype.birthday = function () {
  this.age += 1
  return `${this.name}现在${this.age}岁了。`
}


// ===== 2. 猫 =====

function Cat(name, age, personality) {
  // 初始化父级实例属性
  Animal.call(this, name, age)

  this.personality = personality
}

// 连接原型链,不执行 Animal 构造函数
Cat.prototype = Object.create(Animal.prototype)

// 修复 constructor
Object.defineProperty(Cat.prototype, 'constructor', {
  value: Cat,
  writable: true,
  configurable: true,
  enumerable: false,
})

Cat.prototype.species = '猫'

Cat.prototype.meow = function () {
  return `${this.name}:喵~`
}

// 重写方法,并调用父级实现
Cat.prototype.introduce = function () {
  const basicInfo = Animal.prototype.introduce.call(this)

  return `${basicInfo}我是一只猫,性格${this.personality}。`
}


// ===== 3. 美短 =====

function AmericanShorthair(name, age, personality, coatColor) {
  Cat.call(this, name, age, personality)

  this.coatColor = coatColor
}

AmericanShorthair.prototype = Object.create(Cat.prototype)

Object.defineProperty(AmericanShorthair.prototype, 'constructor', {
  value: AmericanShorthair,
  writable: true,
  configurable: true,
  enumerable: false,
})

AmericanShorthair.prototype.breed = '美国短毛猫'

AmericanShorthair.prototype.showCoat = function () {
  return `${this.name}的毛色是${this.coatColor}。`
}

AmericanShorthair.prototype.introduce = function () {
  const catInfo = Cat.prototype.introduce.call(this)

  return `${catInfo}我的品种是${this.breed},毛色是${this.coatColor}。`
}


// ===== 4. 创建灰灰 =====

const huhui = new AmericanShorthair(
  '灰灰',
  10,
  '安静',
  '银虎斑',
)

huhui.addFavoriteFood('小鱼干')
huhui.addFavoriteFood('鸡肉')

console.log(huhui.introduce())
// 我叫灰灰,今年10岁。我是一只猫,性格安静。
// 我的品种是美国短毛猫,毛色是银虎斑。

console.log(huhui.meow())     // 灰灰:喵~
console.log(huhui.showCoat()) // 灰灰的毛色是银虎斑。
console.log(huhui.favoriteFoods) // ['小鱼干', '鸡肉']

2. class 继承 ​

js
// ===== 1. 动物 =====

class Animal {
  constructor(name, age) {
    this.name = name
    this.age = age
    this.isAlive = true

    // 每个实例拥有独立数组
    this.favoriteFoods = []
  }

  // 类方法自动定义在原型上
  introduce() {
    return `我叫${this.name},今年${this.age}岁。`
  }

  addFavoriteFood(food) {
    this.favoriteFoods.push(food)
  }

  birthday() {
    this.age += 1
    return `${this.name}现在${this.age}岁了。`
  }
}

// 普通数据属性手动放在原型上
Animal.prototype.kingdom = '动物界'


// ===== 2. 猫 =====

class Cat extends Animal {
  constructor(name, age, personality) {
    // 先调用 super,才能使用 this
    super(name, age)

    this.personality = personality
  }

  meow() {
    return `${this.name}:喵~`
  }

  // 重写方法,并调用父级实现
  introduce() {
    const basicInfo = super.introduce()

    return `${basicInfo}我是一只猫,性格${this.personality}。`
  }
}

Cat.prototype.species = '猫'


// ===== 3. 美短 =====

class AmericanShorthair extends Cat {
  constructor(name, age, personality, coatColor) {
    super(name, age, personality)

    this.coatColor = coatColor
  }

  showCoat() {
    return `${this.name}的毛色是${this.coatColor}。`
  }

  introduce() {
    const catInfo = super.introduce()

    return `${catInfo}我的品种是${this.breed},毛色是${this.coatColor}。`
  }
}

AmericanShorthair.prototype.breed = '美国短毛猫'


// ===== 4. 创建灰灰 =====

const huhui = new AmericanShorthair(
  '灰灰',
  10,
  '安静',
  '银虎斑',
)

huhui.addFavoriteFood('小鱼干')
huhui.addFavoriteFood('鸡肉')

console.log(huhui.introduce())
// 我叫灰灰,今年10岁。我是一只猫,性格安静。
// 我的品种是美国短毛猫,毛色是银虎斑。

console.log(huhui.meow())     // 灰灰:喵~
console.log(huhui.showCoat()) // 灰灰的毛色是银虎斑。
console.log(huhui.favoriteFoods) // ['小鱼干', '鸡肉']

3. 在控制台查看灰灰的原型链 ​

在浏览器控制台运行寄生组合式继承示例后,执行 console.dir(huhui) 展开:

灰灰实例在 DevTools 中的原型链结构

个人面试题归档,持续更新中