JavaScript 中对象 setter 无限递归导致栈溢出的解决方案

3次阅读

JavaScript 中对象 setter 无限递归导致栈溢出的解决方案

本文详解 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; // &#9888;&#65039; &#21361;&#38505;&#65281;&#27492;&#22788;&#20877;&#27425;&#35302;&#21457; set age()   } }

当执行 user.age = 20 时,流程如下:
→ 调用 set age(20)
→ 执行 else 分支中的 this.age = 20
→ 再次调用 set age(20)(无限循环
→ 栈深度超限,报错。

✅ 正确做法是:为受控属性声明一个独立的内部存储属性(通常以下划线 _ 开头),setter 写入它,getter 读取它。这既规避了递归,又保持了属性访问的封装性。

以下是修复后的完整、可运行代码:

立即学习Java免费学习笔记(深入)”;

let user = {   name: "Fra",   surname: "Emme",    // &#9989; &#20351;&#29992; _age &#23384;&#20648;&#30495;&#23454;&#20540;&#65292;&#36991;&#20813;&#36882;&#24402;   set age(value) {     if (value < 18) {       console.log(`${value} - You are underage :(`);     } else {       this._age = value; // &#9989; &#20889;&#20837;&#20869;&#37096;&#23646;&#24615;     }   },    get age() {     return this._age; // &#9989; &#35835;&#21462;&#20869;&#37096;&#23646;&#24615;   },    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; // &#9989; &#21516;&#29702;&#22788;&#29702; place   },    get place() {     return this._place;   } };  // &#27979;&#35797; user.age = 20;        // &#9989; &#25104;&#21151;&#35774;&#32622; _age = 20 user.place = "Rovereto"; // &#9989; &#25104;&#21151;&#35774;&#32622; _place = "Rovereto" console.log(user);    // { name: "Fra", surname: "Emme", _age: 20, _place: "Rovereto" } console.log(user.age); // 20&#65288;&#36890;&#36807; getter &#35775;&#38382;&#65289;

? 关键注意事项

  • 下划线前缀(如 _age)仅为命名约定,不提供真正私有性(es6+ 中可结合 # 私有字段实现强封装,但需注意兼容性);
  • 若仅需 setter 而无需 getter,仍建议保留 _age,否则外部无法安全读取该值;
  • 对于 place 等类似 setter,同样必须配对定义 get place() 并统一操作 _place,否则 user.place 将返回 undefined
  • 严格校验逻辑(如空值、长度)应保留在 setter 中,确保数据一致性。

? 总结:setter 的核心职责是拦截并控制赋值行为,而非自我赋值。始终将实际数据存入一个与 setter 名称不同的属性中——这是避免栈溢出的黄金法则。

text=ZqhQzanResources