可利用 Object.defineProperties 一次性定义多个协同的 getter/setter 实现字段联动,需用闭包或 WeakMap 封装私有状态、避免循环触发、加入校验与错误反馈,并配合 Object.freeze 锁定结构。

可以利用 Object.defineProperties 一次性为业务实体对象定义多个带有逻辑联动的 getter/setter,实现字段间自动同步、数据校验、派生计算等复合行为,关键在于描述符中合理组织访问器函数逻辑。
明确业务场景再设计访问器依赖关系
比如一个订单实体需满足:修改 quantity 或 unitPrice 时自动更新 totalAmount;设置 totalAmount 时反向推算 quantity(假设单价锁定);所有数值字段拒绝负值。这时三个属性存在双向约束,不能孤立定义,需在描述符中体现协同逻辑。
- 先梳理字段依赖图:totalAmount 依赖 quantity × unitPrice;quantity 可被 totalAmount 反推(当 unitPrice 固定时)
- 避免循环触发:setter 中修改另一属性时,应跳过重复校验或使用内部标记位
- 把共享校验逻辑(如非负检查)抽成私有工具函数,避免描述符内代码冗余
用闭包或 WeakMap 封装私有状态
访问器需读写“真实值”,但又不能暴露原始属性名(否则绕过逻辑)。推荐用闭包变量或 WeakMap 存储私有数据,确保封装性与内存安全。
- 闭包方式适合单例对象:在 defineProperties 外层函数中声明
let _quantity = 0, _unitPrice = 0,所有 getter/setter 共享这些变量 - WeakMap 方式支持多实例:创建
const privateStore = new WeakMap(),每次初始化实体时privateStore.set(obj, { quantity: 0, unitPrice: 0 }),访问器通过privateStore.get(this)拿状态 - 不建议用下划线前缀属性(如
_quantity),它只是约定,无法阻止外部直接赋值破坏逻辑
在描述符中实现条件式响应与错误反馈
复合访问器不是简单映射,要根据上下文决定行为。例如:仅当 isEditable 为 true 时才允许修改 quantity;totalAmount 的 setter 在 unitPrice === 0 时抛出明确错误而非静默失败。
- getter 中可返回计算结果、默认值或 throw Error(如依赖字段未就绪)
- setter 中优先做输入校验(类型、范围、业务规则),校验失败直接 throw,不要默默修正
- 对派生字段(如
isOverBudget)的 getter,应避免副作用(如发起网络请求),保持纯函数特性
配合 Object.freeze 确保描述符不可篡改
定义完所有访问器后,调用 Object.freeze(entity) 锁定属性结构——防止后续意外添加/删除属性破坏封装,也避免他人用 entity.quantity = xxx 绕过 setter(freeze 不影响 setter 执行)。
- 注意:freeze 只冻结自有属性,若实体有原型链上的可写属性,仍可能被修改,必要时连同原型一起冻结或使用 class + # 私有字段替代
- 调试时可用
Object.getOwnPropertyDescriptors(entity)快速查看当前所有描述符配置是否符合预期 - 若需后期动态扩展,改用
Proxy更灵活,但 defineProperties 更轻量、兼容性更好
不复杂但容易忽略:访问器描述符一旦定义,默认是不可重写(writable: false)、不可枚举(enumerable: false)、不可配置(configurable: false)。若需 for...in 遍历到这些字段,记得显式设 enumerable: true。










