
本文详解 javascript 对象 setter 中因未使用私有存储属性而引发的无限递归问题,通过引入带下划线前缀的内部属性(如 `_age`)彻底避免 `maximum call stack size exceeded` 错误,并提供可运行示例与关键注意事项。
在 JavaScript 中,为对象定义 setter 时若在 setter 内部直接赋值给同一属性名(如 this.age = value),将触发该 setter 的再次调用,从而形成无限递归——每次调用都压入新的执行上下文到调用栈,最终超出引擎限制,抛出 RangeError: Maximum call stack size exceeded。
以原始代码为例:
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)(无限循环)
→ 栈深度超限,报错。
✅ 正确做法是:为受控属性声明一个独立的内部存储属性(通常以下划线 _ 开头),setter 写入它,getter 读取它。这既规避了递归,又保持了属性访问的封装性。
以下是修复后的完整、可运行代码:
立即学习“Java免费学习笔记(深入)”;
let user = {
name: "Fra",
surname: "Emme",
// ✅ 使用 _age 存储真实值,避免递归
set age(value) {
if (value < 18) {
console.log(`${value} - You are underage :(`);
} else {
this._age = value; // ✅ 写入内部属性
}
},
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; // ✅ 同理处理 place
},
get place() {
return this._place;
}
};
// 测试
user.age = 20; // ✅ 成功设置 _age = 20
user.place = "Rovereto"; // ✅ 成功设置 _place = "Rovereto"
console.log(user); // { name: "Fra", surname: "Emme", _age: 20, _place: "Rovereto" }
console.log(user.age); // 20(通过 getter 访问)? 关键注意事项:
- 下划线前缀(如 _age)仅为命名约定,不提供真正私有性(ES6+ 中可结合 # 私有字段实现强封装,但需注意兼容性);
- 若仅需 setter 而无需 getter,仍建议保留 _age,否则外部无法安全读取该值;
- 对于 place 等类似 setter,同样必须配对定义 get place() 并统一操作 _place,否则 user.place 将返回 undefined;
- 严格校验逻辑(如空值、长度)应保留在 setter 中,确保数据一致性。
? 总结:setter 的核心职责是拦截并控制赋值行为,而非自我赋值。始终将实际数据存入一个与 setter 名称不同的属性中——这是避免栈溢出的黄金法则。










