核心思路是不直接修改原对象,而是用不可变或受控可变方式扩展:优先Object.assign创建新对象;必要时用Object.getOwnPropertyDescriptor检查后defineProperty;DOM元素改用data-属性或WeakMap存储;WeakMap最安全;扩展属性加前缀并用defineProperty控制;启用严格模式和TypeScript校验。

核心思路是:**不直接修改原对象,而是用不可变或受控可变的方式扩展**。模块模式下动态加属性容易撞上已有保留属性(比如 constructor、__proto__、toString 等),尤其在严格模式或现代框架中,覆盖这些属性会直接抛错或引发隐性行为异常。
明确区分“扩展”与“赋值”
动态扩展 ≠ 直接给对象挂属性。应避免 obj.newProp = value 这类裸赋值,尤其当 obj 来自第三方库、DOM 元素或内置构造器实例时。
- 优先使用
Object.assign({}, original, extension)创建新对象,原对象保持冻结 - 若必须复用原对象,先检查属性是否存在且是否为只读:
Object.getOwnPropertyDescriptor(obj, 'prop'),再决定是否defineProperty - 对 DOM 元素等宿主对象,绝不覆盖
id、className、dataset等标准属性;改用data-自定义属性或 WeakMap 存储私有状态
用 WeakMap 隔离扩展数据
这是最安全的运行时扩展方式——不污染原对象,也不依赖属性名,天然避开覆盖风险。
- 创建一个全局 WeakMap 实例:
const extensions = new WeakMap() - 扩展时存入:
extensions.set(targetObj, { customField: 'value' }) - 读取时获取:
const data = extensions.get(targetObj) - WeakMap 的键是对象引用,自动随目标销毁而清理,无内存泄漏隐患
预设白名单 + 属性描述符控制
如果业务确实需要往对象上挂属性,就主动约束命名和行为:
- 约定扩展属性统一加前缀,如
$meta、_ext_,与常规属性隔离 - 用
Object.defineProperty显式设置writable: false或configurable: false,防止后续误写 - 对关键对象(如事件对象、配置对象)封装工厂函数:
createExtended(obj, { $traceId: 'xxx' }),内部做校验和防御性复制
启用严格模式并配合 TypeScript 编译时检查
运行时防御靠机制,开发阶段靠提示:
- 模块顶部加
"use strict",让覆盖只读属性立即报错,而不是静默失败 - TypeScript 中为对象类型添加索引签名限制,例如:
interface Extendable { [key: string]: unknown; } & { id: string; },再配合noImplicitAny和strict模式 - 用 ESLint 规则
no-prototype-builtins和no-restricted-properties拦截对保留属性的直接访问或赋值

















