使用defineProperty扩展getter/setter触发无限循环的原因与解决方案
问题成因
触发无限循环的核心原因有两点:
- 你找到的原
attribute属性本身是访问器属性(自带get逻辑),你将原属性的访问器描述符直接完整赋值给了obj的Symbol属性。当你执行obj[id]读取这个Symbol属性时,会自动触发原访问器的get逻辑,此时该get逻辑的this指向obj。 - 当
obj和你重定义属性的目标对象_object是同一个对象时(即_object自身就带有attribute访问器属性的场景),原get逻辑中只要有访问this.attribute的操作,就会触发你新定义的getter,新getter又会再次调用obj[id]执行原get逻辑,形成无限递归,最终导致页面崩溃。
额外注意:你当前用箭头函数定义新的getter,箭头函数没有自身的this,会绑定外层作用域的this,也会导致属性访问的this指向不符合预期。
可行解决方案
不需要额外将原属性拷贝到Symbol名下,直接缓存原访问器的get/set函数,调用时手动绑定当前实例的this即可规避循环问题,修改后的代码如下:
// 查找原型链上的attribute属性描述符逻辑不变 let obj = _object; while(obj && !Object.getOwnPropertyDescriptor(obj, 'attribute')){ obj = Object.getPrototypeOf(obj); } const attributeDesc = Object.getOwnPropertyDescriptor(obj, 'attribute'); // 缓存原访问器的get/set逻辑 const originalGet = attributeDesc.get; const originalSet = attributeDesc.set; // 重定义属性,使用普通函数获取正确的实例this Object.defineProperty(_object, 'attribute', { get: function () { // 手动调用原getter,绑定当前实例为this,不走属性访问逻辑避免循环 const attribute = originalGet.call(this); // 执行你的扩展逻辑 attribute['extend'] = 'property'; return attribute; }, // 补上原setter逻辑,避免原属性的赋值能力丢失 set: function (val) { originalSet.call(this, val); }, enumerable: false, configurable: true });
如果确实需要通过Symbol属性存储原逻辑,要确保读取Symbol属性时不要触发原访问器的this指向已重写attribute的对象,优先推荐上面的缓存方法,性能更高也更安全。
内容的提问来源于stack exchange,提问作者Bálint Rétthy
相关产品推荐
相关产品推荐

