
在扩展原生 map 类时,子类重写的 set 方法会在 super(arg) 构造阶段被内部迭代器反复调用,导致意外执行;可通过构造完成标志位控制逻辑分支,实现构造期使用父类 set、运行期启用自定义逻辑。
在扩展原生 map 类时,子类重写的 set 方法会在 super(arg) 构造阶段被内部迭代器反复调用,导致意外执行;可通过构造完成标志位控制逻辑分支,实现构造期使用父类 set、运行期启用自定义逻辑。
当继承 JavaScript 原生 Map 并重写 set() 方法时,一个常见陷阱是:在 constructor 中调用 super(arg)(例如传入二维数组 [['k','v']])会触发 Map 内部遍历并逐项调用 this.set(key, value) —— 此时 this 已指向子类实例,因此实际执行的是你重写的 set 方法,而非 Map.prototype.set。这会导致日志重复打印、副作用提前发生,甚至因逻辑不完整引发错误。
解决的核心思路是区分“构造中”与“构造后”两个生命周期。最简洁可靠的方式是引入一个布尔标志位(如 this.ready),在 super(arg) 执行完毕后再将其置为 true,并在 set() 中优先检查该状态:
class ModifiedMap extends Map {
constructor(arg) {
super(arg); // ⚠️ 此时 Map 内部会调用 this.set() —— 但此时 this.ready 还未定义/为 false
this.ready = true; // ✅ 构造完成,开启自定义逻辑
}
set(key, value) {
if (this.ready) {
console.log("doing some work here");
// ✅ 可在此添加验证、日志、通知等增强逻辑
return super.set(key, value); // 别忘了真正存入数据!
} else {
// ⚙️ 构造阶段:退回到原生 Map 行为,确保初始化正确
return super.set(key, value);
}
}
}
// 测试
console.log("Constructing...");
const map = new ModifiedMap([["a", "apple"], ["b", "orange"], ["c", "banana"]]);
// → 控制台仅输出 "Constructing...",无重复日志
console.log("Done constructing, adding an entry");
map.set("x", "y");
// → 输出:"doing some work here"(仅一次)⚠️ 注意事项:
-
必须显式调用
super.set():无论是否ready,都需委托给父类完成实际键值存储,否则Map内部状态将不一致(如size不更新、后续get()失败)。 -
标志位不可用
undefined或null检测:应明确初始化为false或使用hasOwnProperty等更健壮判断,避免原型链干扰。 -
不推荐覆盖
set后完全绕过父类逻辑:这会破坏Map的契约(如迭代顺序、size同步、has()/delete()一致性),极易引发隐蔽 bug。
? 更优实践建议:若定制逻辑较复杂(如需要校验、转换、事件派发),可考虑将增强逻辑封装为独立方法(如 enhancedSet()),而 set() 保持最小侵入性;或直接组合 Map 而非继承(Composition over Inheritance),通过代理模式(Proxy)或包装类提供扩展能力——既保持原生 Map 的可靠性,又获得灵活的拦截控制权。

















