直接挂函数表达式易覆盖toString等内置方法,导致序列化异常等;应检查key是否在原型链上、用Object.defineProperty设不可写、Proxy拦截或WeakMap存储。

直接给对象挂函数表达式时,最容易踩的坑就是不小心覆盖了 toString、valueOf、hasOwnProperty 这类内置方法——它们不是“普通属性”,而是 JavaScript 原型链上关键的系统保留方法。一旦被覆盖,会导致 JSON 序列化异常、类型判断失灵、甚至 for...in 遍历出错。
检查目标属性是否属于原型链上的保留方法
在赋值前主动检测,是最轻量也最可靠的预防手段:
- 用
Object.prototype.propertyIsEnumerable.call(obj, key)判断该 key 是否是对象自身可枚举属性(排除原型方法) - 更稳妥的做法:检查
key in Object.prototype或key in Function.prototype,比如:
if (key in Object.prototype || key in Function.prototype) { throw new Error(`禁止覆盖系统保留方法: ${key}`); } - 对常见高危名做白名单拦截,如
['toString', 'valueOf', 'constructor', '__proto__', 'prototype', 'length']
使用 Object.defineProperty 避免意外可写/可配置
动态添加方法时,不走 obj[key] = fn 这种松散赋值,改用定义式方式控制行为:
- 设
writable: false和configurable: false,防止后续被覆盖或删除 - 显式指定
enumerable: true控制是否出现在for...in中 - 示例:
Object.defineProperty(obj, 'customMethod', { value: () => {}, writable: false, configurable: false, enumerable: true });
统一通过代理层拦截危险操作
若需频繁扩展对象且环境可控(如工具库内部),可用 Proxy 封装一层防护:
- 拦截
set操作,在赋值前校验 key 是否为保留名 - 对已知风险名直接拒绝,或自动重命名(如转为
_custom_toString) - 注意:Proxy 无法代理非对象类型,且会带来轻微性能开销,适合关键对象而非高频基础类型
优先使用 Map 或 WeakMap 存储动态行为
如果扩展目的只是“给某个对象临时绑定行为”,根本不必污染对象自身属性:
- 用
WeakMap以对象为键、函数为值,完全避开属性名冲突问题 - 例如:
const extensions = new WeakMap();
extensions.set(targetObj, { customRun() { ... } }); - 既无覆盖风险,又自动随原对象回收,内存安全

















