原型继承和 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.prototype,Dog -> Animal。class底层还是原型,但语义更严格,面试要能说出super和 TDZ。