属性描述符详解


属性描述符详解

一、什么是属性描述符?

属性描述符(Property Descriptor)是 JavaScript 中用于描述对象属性的隐藏特性的配置对象。

在 JavaScript 中,对象的每个属性都有一组描述其行为的特性,这些特性默认是隐藏的,我们需要通过特定的方法来查看和修改它们。

二、属性描述符的分类

属性描述符分为两大类:

  1. 数据属性描述符:用于描述普通的数据属性
  2. 访问器属性描述符:用于描述带有 getter/setter 的属性

三、如何定义属性描述符

3.1 单个属性:Object.defineProperty

使用 Object.defineProperty() 方法可以为对象定义或修改单个属性的描述符:

3.2 批量属性:Object.defineProperties

如果要同时定义多个属性的描述符,可以使用 Object.defineProperties() 方法:

3.3 修改属性描述符:Object.defineProperty

如果需要修改一个属性的描述符,可以使用 Object.defineProperty() 方法:

四、数据属性的 4 个特性详解

数据属性有 4 个核心特性,我们一个一个来讲,每个特性都配一个示例。

4.1 value:属性的值

这个最简单,就是属性存储的具体值,我们读取属性时返回的就是这个值。

4.2 writable:是否可修改

控制属性的值是否可以通过 对象.属性 = 值 的方式被修改。

示例:对比 writable 为 true 和 false 的区别

严格模式下的表现

4.3 enumerable:是否可枚举

控制属性是否可以被 for...in 循环或 Object.keys() 遍历到。

示例:对比 enumerable 为 true 和 false 的区别

4.4 configurable:是否可配置

控制属性是否可以被删除,以及是否可以修改属性的其他特性(唯一例外:可以将 writable 从 true 改为 false)。

示例:对比 configurable 为 true 和 false 的区别

五、访问器属性(getter 和 setter)

访问器属性不直接存储值,而是通过两个函数来控制属性的读取和设置:

  • get:读取属性时自动调用的函数,返回值就是属性的读取结果
  • set:设置属性时自动调用的函数,接收新值作为参数

5.1 基础示例

对比:普通属性 vs 访问器属性

5.2 进阶:用 setter 做数据验证

访问器属性的一个常见用途是在设置值时做校验,保证数据的合法性。

六、如何查看属性描述符?

我们可以使用 JS 内置的方法,查看某个属性完整的描述符配置。

6.1 查看单个属性的描述符

使用 Object.getOwnPropertyDescriptor()

6.2 查看对象所有自身属性的描述符

使用 Object.getOwnPropertyDescriptors()

七、属性描述符的常见应用场景

7.1 实现只读常量属性

7.2 实现计算属性

7.3 实现数据监听与响应式

这也是 Vue2 响应式系统的核心原理,通过 get/set 监听数据的读写,在数据变化时触发视图更新。

八、注意事项

8.1 Object.defineProperty () 的默认值

当你使用 Object.defineProperty() 定义属性时,没有手动设置的特性,默认值全都是 false,和字面量赋值的默认值完全相反!

对比:字面量赋值 vs Object.defineProperty 不指定特性

正确写法:需要什么特性,就手动明确写出来,不要依赖默认值!

8.2 configurable: false 是不可逆的!

一旦你把某个属性的 configurable 设为 false,就再也无法把它改回 true 了,这是一个单向操作,不可逆!

8.3 writable: false 不是绝对的 “只读”

writable: false 时,直接用 对象.属性 = 值 的方式赋值会失败,但如果 configurable: true,你依然可以通过 Object.defineProperty() 来修改属性的 value

8.4 数据属性和访问器属性不能混用!

属性描述符分为数据属性描述符和访问器属性描述符,两者是互斥的,不能在同一个属性的描述符里同时出现!

  • 数据属性专属配置:value、writable
  • 访问器属性专属配置:get、set

8.5 for…in 会遍历原型链上的可枚举属性

enumerable: false 只会让当前属性不被遍历,但如果你的对象原型上有可枚举的属性,for...in 循环依然会遍历到!

正确写法:用 hasOwnProperty() 判断属性是不是当前对象自身的,过滤掉原型链上的属性。

8.6 非严格模式下的静默失败

在非严格模式下,很多违反属性描述符规则的操作,不会报错,只会静默失败。开发时一定要开启严格模式(在代码顶部加 "use strict";),所有违规操作都会直接抛出明确的报错。

8.7 修改数组 length 属性的描述符

数组的 length 属性本身就有默认的属性描述符:writable: trueenumerable: falseconfigurable: false。非特殊需求,绝对不要修改数组 length 属性的描述符!

8.8 原型上的属性描述符会影响所有实例

如果你给构造函数的原型对象,用 Object.defineProperty 定义了属性,那么这个属性的描述符会影响所有的实例对象!

8.9 Object.defineProperty 的局限性

Object.defineProperty 有一些局限性:

  • 只能监听对象已有属性的读写,无法监听新增属性、删除属性
  • 对数组的监听能力有限,无法监听通过下标修改数组元素、修改数组 length 的操作
  • 只能监听单个属性,需要递归遍历对象的所有属性,性能开销较大

如果需要完整监听对象的所有变化,ES6 提供了更强大的 Proxy 对象,它可以拦截整个对象的所有操作,解决了 Object.defineProperty 的所有局限性,也是 Vue3 响应式的核心原理。

九、总结

属性描述符是 JS 用来控制对象属性隐藏特性的配置对象,分为数据属性描述符和访问器属性描述符两类,二者互斥不可混用。

数据属性的 4 个核心特性:

  • value:属性存储的值
  • writable:控制属性是否可被直接赋值修改
  • enumerable:控制属性是否可被遍历
  • configurable:控制属性是否可被配置、删除,设为 false 后不可逆

访问器属性:

通过 get(读取触发)和 set(赋值触发)函数控制属性读写,严禁在 get/set 内直接访问同名属性,避免无限递归。

核心操作方法:

  • 单个属性配置:Object.defineProperty()
  • 批量属性配置:Object.defineProperties()
  • 查看属性描述符:Object.getOwnPropertyDescriptor() / Object.getOwnPropertyDescriptors()

核心避坑原则:

  • Object.defineProperty 时,手动明确写出所有特性,不依赖默认值
  • 开发时开启严格模式,避免静默失败导致的调试困难
  • 非必要不设置 configurable: false,避免不可逆的操作
  • 访问器属性必须用不同名的内部属性存储真实值,杜绝无限递归

文章作者: 栖桐听雨声
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 栖桐听雨声 !
  目录