ES6 extends 被编译后的 JavaScript 大概长什么样?
面试速答(30 秒版 TL;DR)
class Child extends Parent {}的本质是两条链:- 实例链:
childInstance.[[Prototype]] === Child.prototype,并且Child.prototype.[[Prototype]] === Parent.prototype - 构造函数链(静态链):
Child.[[Prototype]] === Parent
- 实例链:
- 编译到 ES5(以 TypeScript/Babel 为例)通常会生成:
- 一个
__extends/_inherits的 helper:把Child.prototype的原型指向Parent.prototype,并修正constructor - 一个设置静态继承的步骤:让
Child的原型指向Parent(复制或Object.setPrototypeOf) super(...)变成调用父构造函数,并保证this初始化时机正确
- 一个
- 具体产物取决于:编译器(TS/Babel)、目标(
target)、是否启用loose、是否需要reflect-metadata等。
先记住“extends 做了什么”(不依赖具体编译器输出)
你在面试里可以用两句话概括:
Child.prototype通过原型链指向Parent.prototype,因此实例能访问父类原型方法。Child自身的原型链指向Parent,因此静态方法/属性也能被“继承”。
一个代表性示意:TypeScript 编译到 ES5(简化版)
下面代码是“结构示意”,不保证与任意版本输出逐字一致,但核心步骤是一致的。
var __extends =
(this && this.__extends) ||
function (Child, Parent) {
// 1) 静态继承:Child.__proto__ = Parent(或复制属性)
Object.setPrototypeOf(Child, Parent);
// 2) 原型继承:Child.prototype.__proto__ = Parent.prototype
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
};
var Parent = /** @class */ (function () {
function Parent(name) {
this.name = name;
}
Parent.prototype.say = function () {
return this.name;
};
return Parent;
})();
var Child = /** @class */ (function (_super) {
__extends(Child, _super);
function Child(name) {
return _super.call(this, name) || this; // super(name)
}
return Child;
})(Parent);
一个代表性示意:Babel 编译(常见会引入 helper)
常见 helper 组合包括 _inherits、_createSuper、_possibleConstructorReturn、_getPrototypeOf 等,用来正确处理:
super()返回对象时的返回值语义- 派生类在
super()之前不能访问this new.target、原生内建类型继承等边界
面试不需要背 helper 名字,讲清“为什么需要 helper”即可。
常见追问
Q1:为什么派生类必须先 super() 才能用 this?
因为在规范里,派生类的 this 初始化由父类构造函数完成;编译到 ES5 时 helper 也会围绕这一点确保时序正确。
Q2:extends 和“寄生组合继承”是什么关系?
如果你把 extends 的效果拆开看,它和“寄生组合继承”非常接近:都在做“实例链 + 静态链”的原型设置,只是 class 语法还会处理更多语义边界。
速记要点(可背诵)
extends干两件事:Child.prototype -> Parent.prototype,以及Child -> Parent(静态)。- 编译产物因工具链不同而不同,但核心都是设置两条原型链并保证
super/this语义正确。