文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载在 JavaScript 中当一个类通过extends继承另一个类并重写了自己的constructor时new一个实例往往会抛出Error: this is not defined之类的错误。本指南以现代 JavaScript 教程zh.javascript.info中 类继承一章 的经典习题“创建实例时出错”为线索从题目、错误现象出发深入剖析派生构造器derived constructor的特殊内部机制给出标准修正方案并结合仓库内的ExtendedClock实战任务演示真实项目中如何正确书写子类构造器。读完本文你将彻底理解“子类 constructor 必须先调用super()才能使用this”这条铁律的底层原因并能熟练写出健壮的继承类。题目重现无法创建的 Rabbit 对象仓库中的 task.md 给出了一段看似合理、实际却无法运行的代码class Animal { constructor(name) { this.name name; } } class Rabbit extends Animal { constructor(name) { this.name name; this.created Date.now(); } } let rabbit new Rabbit(White Rabbit); // Error: this is not defined alert(rabbit.name);运行new Rabbit(White Rabbit)时JavaScript 引擎直接抛出Error: this is not defined。从直觉上看Rabbit的构造器只是在重复Animal构造器的逻辑设置this.name再额外记录created时间戳为何会报错关键点在于继承类的 constructor 必须调用super(...)并且必须在任何this访问之前调用。这份代码没有调用super()所以引擎无法为Rabbit实例创建this对象任何对this的读写都无从谈起。根因剖析派生构造器的[[ConstructorKind]]: derived为什么普通类不需要super()而继承类必须要类继承主教程 给出了规范的级解释在 JavaScript 内部继承类的构造函数即“派生构造器”derived constructor与其他函数存在本质区别——它带有一个特殊的内部标签[[ConstructorKind]]: derived。这个标签直接改变new的行为构造器类型new时的行为是否需要super()常规函数 / 基类构造器引擎自动创建一个空对象并赋值给this不需要派生构造器extends的子类不创建对象不初始化this期望父类构造器来完成必须调用super()也就是说当通过new执行一个派生构造器时JavaScript 引擎不会像对待普通函数那样先创建一个空对象并绑定为this而是把创建实例对象的任务“移交”给父类构造器。super(...)的作用就是执行父类base的 constructor让它完成this对象的创建与初始化。如果跳过super()this就始终处于未定义状态一旦代码尝试访问this.name便会立刻报错。无 constructor 时引擎自动补全主教程还特别指出如果一个继承类没有声明自己的constructor根据 ECMAScript 规范引擎会自动生成如下“空”构造器class Rabbit extends Animal { // 为没有自己的 constructor 的扩展类生成的 constructor(...args) { super(...args); } }它会调用父类构造器并透传全部参数这也是为什么不写 constructor 时继承一切正常的原因。问题只出现在子类自己声明了 constructor 却忘了调用super()的场景。修正方案先super()再使用this仓库中 solution.md 给出了标准修正核心就是在访问this之前先调用super(name)class Animal { constructor(name) { this.name name; } } class Rabbit extends Animal { constructor(name) { super(name); // 先调用父类构造器创建并初始化 this this.created Date.now(); // 然后才能使用 this } } let rabbit new Rabbit(White Rabbit); // 现在好了 alert(rabbit.name); // White Rabbit修正后的执行流程是new Rabbit(White Rabbit)进入派生构造器此时this尚未创建super(name)调用Animal的 constructor由它完成this的创建并执行this.name name控制权返回子类构造器此时this已可用再执行this.created Date.now()最终rabbit实例同时拥有name和created两个属性。这条规则同样适用于带多个参数、带默认值或解构参数的构造器super()必须是子类构造器中第一次触碰this之前的调用顺序不可颠倒。深入理解字段初始化顺序与重写字段的陷阱理解了super()的必需性后还有一个容易踩坑的衍生知识点——类字段的初始化顺序。主教程用一个例子揭示了继承体系下字段与方法行为的差异class Animal { name animal; constructor() { alert(this.name); // (*) } } class Rabbit extends Animal { name rabbit; } new Animal(); // animal new Rabbit(); // animal而不是 rabbit字段初始化的规则是基类未继承任何类的类字段在构造器调用之前初始化派生类字段在super()返回之后立刻初始化。因此当Rabbit没有自己的 constructor等价于空构造器super(...args)时new Rabbit()会先执行Animal的构造器而此刻Rabbit的name rabbit字段尚未初始化所以父类构造器读到的永远是父类自己的字段值animal。作为对比若父类构造器调用的是this.showName()方法由于方法查找沿原型链进行子类重写的方法会被命中输出就会是rabbit。这就是 JavaScript 中“字段按值绑定、方法按原型链解析”的微妙差异。若业务上必须让父类构造器感知子类字段建议改用方法或 getter/setter 而非类字段。实战验证用super()正确扩展 Clock 类仓库中紧邻的第二个任务 扩展 clock 是super()在实际代码中的典型用法在不修改父类的前提下扩展其能力。父类 clock.js 每秒打印一次时间其 constructor 接收一个解构参数{ template }class Clock { constructor({ template }) { this.template template; } // render / stop / start ... }子类 extended-clock.js 新增precision两次 tick 之间的毫秒数默认 1000构造器先调用super(options)完成父类初始化再设置自己的字段class ExtendedClock extends Clock { constructor(options) { super(options); let { precision 1000 } options; this.precision precision; } start() { this.render(); this.timer setInterval(() this.render(), this.precision); } };注意这里三个关键点super(options)把完整参数对象透传给父类构造器父类再解构出template子类自己从options中解构出precision并给出默认值1000子类重写了start()将setInterval的间隔从硬编码的1000改为this.precision实现了可配置刷新频率运行入口 index.html 演示了实际使用传入precision: 10000创建每 10 秒才刷新一次的低分辨率时钟。这个例子清晰展示了正确书写派生构造器的完整模式先super(父类所需参数)再初始化子类自有状态。小结与自检清单回到“创建实例时出错”这道题可以总结为如下要点继承类的 constructor 必须调用super(...)且调用必须发生在任何this读写之前根本原因是派生构造器带有内部标签[[ConstructorKind]]: derivednew时不会自动创建this对象该任务被移交给父类构造器子类未声明 constructor 时引擎会自动生成constructor(...args) { super(...args); }派生类的类字段在super()之后才初始化父类构造器内访问重写字段会读到父类值必要时改用方法或 getter/setter实战中如ExtendedClock保持“super(options)透传 → 子类自有状态初始化 → 重写方法”的书写顺序即可避免此类错误。对照代码排查时只需检查子类构造器第一行之前是否出现了this.xxx ...如果出现了就说明super()被放在了错误的位置——把super()提到最前问题即告解决。赞分享文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载相关推荐JavaScript 类继承中重写 constructor 为何报错 this is not definedsuper() 调用的必要性与底层原理JavaScript 类继承中重写 constructor 为何报错 this is not definedsuper 调用的必要性与底层原理 本指南基于文档教程前端JavaScript 类继承派生类构造函数必须调用 super() 的原因与修复JavaScript 类继承派生类构造函数必须调用 super 的原因与修复 派生类 extends 之后的子类自定义构造函数时如果直接给 this 赋文档/教程前端Redux action type 为什么必须用字符串Symbol 类型什么时候允许Redux action type 为什么必须用字符串Symbol 类型什么时候允许 在写 Redux 应用时 store.dispatch 传入的 ac前端上一篇go-metrics高级技巧FanoutSink实现多后端指标分发的完整方案下一篇Egg.js TypeScript实战从JavaScript迁移到TypeScript完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考