
本文详解 `maximum call stack size exceeded` 错误在对象 setter 中的成因——setter 内部直接赋值 `this.age = value` 会无限触发自身,形成隐式递归;并提供规范、安全的修复方案(使用私有属性 + get/set 配对)。
在 JavaScript 中,为对象定义 setter 时若未谨慎处理内部存储逻辑,极易引发 无限递归,最终抛出 RangeError: Maximum call stack size exceeded。该错误并非语法错误,而是运行时堆栈耗尽的致命异常——它无声地揭示了一个经典陷阱:在 setter 方法体内,对同一属性名执行 this.prop = value 赋值操作,会再次触发该 setter 自身。
以原始代码为例:
set age(value) {
if (value < 18) {
console.log(`${value} - You are underage :(`);
} else {
this.age = value; // ⚠️ 危险!此处又调用了 set age()
}
}当执行 user.age = 20 时:
- 触发 set age(20);
- 进入 else 分支,执行 this.age = 20;
- 再次触发 set age(20) → 无限循环 → 栈溢出。
✅ 正确解法是引入独立的内部存储属性(通常以 _ 前缀约定为“私有”),使 getter/setter 操作与实际数据存储解耦:
立即学习“Java免费学习笔记(深入)”;
let user = {
name: "Fra",
surname: "Emme",
set age(value) {
if (value < 18) {
console.log(`${value} - You are underage :(`);
} else {
this._age = value; // ✅ 存入独立属性,不触发 setter
}
},
get age() {
return this._age; // ✅ 读取独立属性
},
get fullName() {
return `${this.name} ${this.surname}`;
},
set place(value) {
if (value === "" || value == null || value.length <= 2) {
console.log("Invalid place");
return;
}
this._place = value; // ✅ 同理,避免 this.place = value
},
get place() {
return this._place;
}
};
user.age = 20;
user.place = "Rovereto";
console.log(user); // { name: "Fra", surname: "Emme", _age: 20, _place: "Rovereto" }? 关键注意事项:
- 必须配对声明 get 和 set:仅定义 set 而无对应 get,会导致 user.age 读取时返回 undefined(因 _age 属性未被初始化且不可见);
- 下划线 _ 是命名约定,非语言强制私有:JS 对象无真正私有属性(ES2022 # 私有字段除外),_age 仍可被外部直接访问,但符合行业实践,明确表达“不应直接操作”;
- 校验逻辑应前置:如 value == null 比 value === null 更健壮(覆盖 null 和 undefined),但需结合业务权衡严格性;
- 避免副作用:setter 内不应包含异步操作或复杂计算,保持其语义为“设置状态”。
? 总结:setter 的核心职责是验证并委托存储,而非自我调用。始终确保写入目标是独立于 accessor 名称的底层属性。这一原则同样适用于 class 中的 set 访问器——它是 JavaScript 面向对象封装中不可绕过的坚实基石。








