在 ES6 class 的构造器中必须先调用 super() 吗?为什么?
派生类构造器通常要在访问 this 前调用 super(),因为父类构造过程负责提供可用的实例,随后才初始化子类字段。若构造器显式返回另一个对象,可以不调用 super(),但得到的对象不会自动拥有原本子类实例的初始化结果。
对象与原型专题面试题第 1 页,显示第 1–35 题,共找到 35 道完整解析,可继续按分类、标签与关键词缩小范围。
按稳定语义路径排序
派生类构造器通常要在访问 this 前调用 super(),因为父类构造过程负责提供可用的实例,随后才初始化子类字段。若构造器显式返回另一个对象,可以不调用 super(),但得到的对象不会自动拥有原本子类实例的初始化结果。
类字段箭头函数在每次创建实例时单独生成,并捕获该实例的 this,适合交给事件回调而不必额外绑定。原型方法由实例共享,方法被提取后 this 由调用方式决定;内存开销、继承覆盖和函数引用稳定性因此各有取舍。
普通构造函数被 new 调用时,显式返回对象或函数会替换默认实例,返回 null 或其他原始值则被忽略。返回外部对象后,原先写在 this 上的属性不会迁移,instanceof 结果也可能改变;class 基类和派生类对返回原始值还有额外限制。
class 的 # 私有字段只能在声明它的类体内部访问,类外直接写 obj.#x 是语法错误。它不会出现在 Object.keys、JSON.stringify 或普通 Proxy 陷阱中;#x in obj 可做品牌检查,而 TypeScript private 主要是编译期限制,两者不能混同。
ES5 时代把 Child.prototype 设成 new Parent() 虽能接上父类方法,也会额外执行父构造函数,并把父实例属性放到子类原型上。用 Object.create 建立原型链,再在 Child 内调用 Parent.call 初始化实例,可把两件事分开处理,避免共享可变状态。
Object.preventExtensions 会阻止对象增加自有属性,已有属性仍可能修改或删除,且这种限制不能撤销。Object.isExtensible 可检查状态;严格模式下添加属性会报错,不可扩展对象的原型也不能随意更换,seal 和 freeze 则施加更强约束。
call 与 apply 都会立即调用函数并指定 this,前者逐个传参,后者传类数组参数;bind 则返回延后调用的新函数。借用数组方法和事件回调中经常遇到这三者,已绑定函数再用 call 通常改不了 this,被 new 调用时又有特殊规则。
Object.getPrototypeOf 是读取原型的标准方式,Object.setPrototypeOf 可在运行时更换原型,却可能破坏引擎优化。多数场景更适合在创建对象时就用 Object.create 或 class 确定继承关系;原型成环和不可扩展对象还会使设置失败。
类中的 get 与 set 会把属性定义成访问器,读取或赋值时自动运行,而不是像普通方法那样显式调用。它们与属性描述符中的访问器共用一套机制,但默认可枚举性不同;同名实例属性遮蔽访问器,或 setter 里给同名属性赋值,都可能造成意外。
in 会沿原型链查找属性,Object.hasOwn 与 hasOwnProperty 只检查自有属性。用 Object.create(null) 做字典或对象覆盖了 hasOwnProperty 方法时,Object.hasOwn 更稳;遍历时还要区分 for...in 的继承键与 Object.keys 的自有键。
Object.is 与 === 只在 NaN 自比和正负零比较上不同;两个对象无论内容多像,仍按引用身份比较。React 会用 Object.is 判断状态是否变化,includes 与 Map 又采用不区分正负零的 SameValueZero,相等规则需结合具体 API 看。
x instanceof C 会优先调用 C 的 Symbol.hasInstance 方法,只有采用默认实现时才按原型链判断。自定义静态方法可让类型判定遵循接口形状,但直接给继承来的只读方法赋值可能失败;跨 iframe 的构造函数身份也仍是另一层边界。
isPrototypeOf 直接检查一个对象是否位于另一对象的原型链上;instanceof 通常取构造函数的 prototype 做相似判断,但还可能由 Symbol.hasInstance 改写。用 Object.create 建立的对象不一定有对应构造函数,替换构造函数原型也会改变后续判断结果。
Object.keys 只返回自有可枚举字符串键;Object.getOwnPropertyNames 包含不可枚举字符串键,Object.getOwnPropertySymbols 返回符号键。Reflect.ownKeys 会合并自有字符串与符号键,for...in 则可能遍历继承属性;应按所需键类型选 API。
JavaScript 的 class 只能继承一个父类,mixin 可把多组独立能力组合进同一类。类工厂通过继承返回新类,Object.assign 只复制可枚举自有属性,因此直接复制类原型通常拿不到方法;多个 mixin 出现同名方法时还需要确定覆盖顺序。
Object.create(null) 生成没有 Object.prototype 的纯字典,不会继承 toString 和 hasOwnProperty,字符串隐式转换也可能失败。它适合避免键名冲突,检查自有键应使用 Object.hasOwn;需要任意类型键、频繁增删或明确遍历语义时可考虑 Map。
属性的 configurable 为 false 后,不能删除、改可枚举性或在数据属性与访问器之间切换。若它仍可写,值还可以改变,writable 也能从 true 降到 false;seal 和 freeze 会批量施加不同程度的类似限制。
可选链 ?. 只在左侧为 null 或 undefined 时短路,返回 undefined;&& 碰到零、空字符串或 false 也会停下,并保留那个假值。访问属性、调用方法和连续链的短路范围并不完全相同,给可能缺失的回调加 ?. 时尤其要看调用对象。
对象自有键的通常顺序是数组索引键升序、其他字符串键按创建顺序,最后是 Symbol 键;"01"、"-1" 和 "1.5" 不属于数组索引键。Object.keys、Reflect.ownKeys 与 JSON.stringify 选择的键范围不同,不能把遍历顺序当作业务排序。
enumerable: false 的属性仍可直接读取,也能被 in 找到,却不会出现在 Object.keys、对象展开或常规 JSON.stringify 结果中。for...in 同样跳过不可枚举属性,但还会遍历继承来的可枚举键;需要列出全部自有键时可用 Reflect.ownKeys。
字面量与 Object.defineProperty 定义的 getter、setter 在读写时都执行函数,但后者省略 enumerable 和 configurable 时都为 false。于是 Object.keys、JSON.stringify 可能看不到它,删除也会失败;Vue 2 的响应式实现曾利用访问器。
给不可写属性赋值,在严格模式中抛 TypeError,非严格模式下通常静默失败;是否严格取决于赋值代码所在环境。原型上的只读属性也可能阻挡子对象同名赋值,Reflect.set 可用布尔返回值识别失败,configurable 与 freeze 又控制另外的边界。
读取对象属性时先查自身,找不到才沿原型链逐层向上,第一次命中即停止,走到 null 仍没有就得到 undefined。普通赋值通常写入对象自身,和读取过程并不对称;继承方法中的 this、运行中改原型以及各种枚举 API 也各有边界。
Proxy 的 get 陷阱接收目标、属性键和接收者,可在读取时返回计算结果或代理本身,因而能实现链式 API。代理 Map 等有内部槽的内置对象时可能报错,方法里的 this 也可能改变;误把 then 属性生成为函数还会影响 await。
Proxy 陷阱不能谎报违反目标对象固定属性约束的结果。对不可写且不可配置的数据属性设置不同值时,set 若声称成功会导致 TypeError;get、has 和 ownKeys 也受对应不变量限制,代理校验必须尊重真实目标状态。
Object.freeze 只冻结对象自身第一层的属性描述符,嵌套对象依旧可以修改。非严格模式下的失败写入可能不报错;递归冻结则需要处理循环引用、Symbol 键与访问器属性,同时留意它仍不能冻结某些内部状态。
Reflect.ownKeys 返回对象自身所有的键,包括不可枚举的和 Symbol 键,顺序是整数键升序、字符串键按插入顺序、Symbol 最后。对比 Object.keys、getOwnPropertyNames、getOwnPropertySymbols 的范围,以及对象键顺序在实际代码里的坑。
Reflect.set 与赋值语句采用相同的对象写入机制,但前者返回成功与否的布尔值,还能显式指定 receiver。只读属性写入失败时,严格模式赋值会抛错,非严格模式可能静默失败;Proxy 的 set 陷阱中把 receiver 传对还会影响 setter 的 this。
Proxy.revocable 返回代理和撤销函数;撤销后读取、写入、删键或列键都会抛 TypeError,但 typeof 仍可返回类型字符串。它适合把对象能力临时交给第三方代码,撤销并不会自动销毁目标对象或切断别处持有的原始引用。
Object.seal 阻止增删属性,已有可写属性仍可改值;Object.freeze 进一步把自有数据属性设为不可写。两者都只处理对象自身一层,嵌套对象照样能变;在非严格模式下,违规写入还可能静默失败。
浅拷贝的新对象仍与原对象共享嵌套引用,深拷贝则尝试复制可达的对象图。递归实现需要用 WeakMap 处理循环引用;JSON 序列化会丢失多种值,structuredClone 也不复制函数与私有字段,选择方案要看数据形状。
对象展开只复制自身可枚举的字符串键与 Symbol 键,不带走原型方法或不可枚举属性。getter 会在复制时执行,结果保存为普通值;类实例被展开后丢方法,和 Object.assign 的差别则主要体现在写入目标的方式。
子类可以继承父类静态方法,因为 extends 也把子类构造函数接到父类构造函数的原型链上。静态方法里的 this 指向调用它的类,super 可访问父类静态方法;实例不能直接调用它,修改继承来的静态字段还可能只遮蔽父类值。
Object.prototype.toString 生成的 [object Type] 标签可受 Symbol.toStringTag 属性影响,内置 Map、Promise 也有各自标签。自定义类型常在原型上提供 getter;由于对象能改写这个标签,不能把它作为不可伪造的安全类型证明。
模块作用域中的 WeakMap 可以用实例作键存内部状态,外部代码拿不到那份映射;只要别处没有强引用,键本身不会被 WeakMap 留住。它与类的 # 私有字段在访问错误、继承、性能和调试体验上仍有区别,选择时要看封装需求。