先谈谈我的认识:属性描述符本质上是 Ja vaScript 用来控制对象行为的一套底层机制。它并非用于炫技,而是为了应对实际开发中必然会遇到的真实痛点——比如数据被意外修改、内部状态被暴露、需要实现响应式逻辑,或者希望让自定义的数据结构像原生类型一样自然易用。

基于多年的项目经验,灵活使用属性描述符,能让自定义数据结构变得可控、可靠且具备良好的演进能力。
封装私有状态,防止外部直接访问
Ja vaScript 长期以来没有真正的 private 字段(直到 #private 出现),但借助 enumerable: false 和 configurable: false,我们可以把内部属性“隐藏”得很彻底。举例来说,设计一个带缓存的计算器:
- 将
_cache设置为不可枚举、不可配置,它不会出现在Object.keys()或 JSON 序列化中; - 配合
writable: false,还能避免被意外覆盖; - 用户通过
calc.result调用 getter 时,内部自动更新缓存,而缓存变量本身对使用者完全不可见。
这套组合策略让内部状态真正做到“眼不见心不烦”。
实现只读或防篡改的数据结构
当自定义类的实例属性需要“固定”下来,比如 ID、创建时间、配置项这类一旦生成就不能更改的属性,属性描述符是最佳选择:
writable: false阻止赋值操作(严格模式下直接报错,非严格模式下静默失败);- 加上
configurable: false后,连defineProperty也无法修改,更不能通过delete删除; - 常见组合为
{ value: xxx, writable: false, configurable: false, enumerable: true }——值固定、可读取、不可删除、不可修改,稳固性极强。
值得注意的是,这种锁定机制在安全性要求较高的配置场景下特别实用。
构建计算属性与副作用控制
访问器描述符(get/set)让属性变成“动态的”。用于自定义结构中,堪称杀手锏:
- 派生字段:例如
fullName自动拼接firstName和lastName; - 格式转换:设置
price时自动转为数字并校验范围,取值时返回带货币符号的字符串; - 触发更新:在
set中调用notifyChange(),为响应式机制手动铺路——这正是 Vue 2 核心原理的微缩版实现。
这正是计算属性的真正魅力所在。
安全复制与继承控制
这一点容易被忽略。使用 Object.assign 进行复制时,会丢失 get/set 以及所有描述符特性,导致自定义行为失效。正确做法是:
- 用
Object.getOwnPropertyDescriptors(obj)获取完整的属性描述符; - 再用
Object.defineProperties(target, descriptors)精确还原; - 如果需要继承原型上的访问器(比如自定义数组类),可以通过
Object.create(proto, descriptors)构造新实例。
数据私有化、行为可控、计算属性、安全复制——这些场景看似简单,但在实际项目中,真正用好这套机制,自定义数据结构就不仅是“能用”,而是实现了真正的“可控、可信赖、可演进”。
