跳到主要内容

原型继承和 class 继承有什么区别?class 是不是语法糖?

面试速答(30 秒版 TL;DR)

  • ES6 class 本质上仍然基于 原型链,可以视为“更接近传统 OOP 的语法糖”,但它也带来一些语义约束(例如类声明存在 TDZ、类方法默认不可枚举、必须 new 调用等)。
  • 两者的继承底层都是:实例对象通过 [[Prototype]] 委托到父类原型,构造函数(或类)之间也通过原型链串起来。
  • 面试回答重点:说清 extends 做了什么、super() 必须先调用的原因,以及“静态方法继承”和“实例方法继承”分别挂在哪。

原型继承(构造函数时代)的典型写法

function Animal(name) {
this.name = name;
}
Animal.prototype.say = function () {
return `I am ${this.name}`;
};

function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

const d = new Dog("Lucky");
d.say(); // "I am Lucky"

要点:

  • Dog.prototype = Object.create(Animal.prototype) 让实例方法沿原型链继承。
  • Animal.call(this, ...) 让构造器逻辑复用(初始化实例属性)。

class 继承的写法

class Animal {
constructor(name) {
this.name = name;
}
say() {
return `I am ${this.name}`;
}
static kind() {
return "animal";
}
}

class Dog extends Animal {
constructor(name) {
super(name);
}
}

面试能说的“底层事实”:

  • Dog.prototype.[[Prototype]] === Animal.prototype(实例方法继承)
  • Dog.[[Prototype]] === Animal(静态方法继承)

关键区别(面试高频点)

1)super() 为什么必须先调用?

在派生类(extends)的构造器里,this 的初始化由父类构造器负责;没 super()this 还不能用。

2)class 的语义更“严格”

  • 类声明存在 TDZ:使用早于声明会报错(不像函数声明那样可随意提升)。
  • 类方法默认不可枚举(更接近“方法”的直觉)。
  • 类只能 new 调用(直接调用会抛错)。

3)原型继承更灵活,但更容易写错

常见坑:

  • 忘记修复 constructor
  • 直接 Dog.prototype = Animal.prototype 导致共享同一个 prototype 对象
  • 忘了调用父构造器初始化实例字段

典型题 & 标准答法

Q1:class 是不是语法糖?

答法模板:

  • “底层仍是原型链,所以从对象模型上看是语法糖。”
  • “但 class 也引入了更严格的语义约束(TDZ、必须 new、super 规则等),所以不能把它当成完全等价的文本替换。”

易错点/坑

  • 误以为 class 是传统意义上的“真正类系统”:JS 仍是原型委托。
  • 在子类构造器里 super() 前访问 this
  • 手写原型继承时把原型对象“指针”赋值导致父子互相污染。

速记要点(可背诵)

  • extends 让两条链成立:Dog.prototype -> Animal.prototypeDog -> Animal
  • class 底层还是原型,但语义更严格,面试要能说出 super 和 TDZ。