JS中的属性描述符

Fri, July 5, 2024 - 7 min read

Object上存在两个API:defineProperty()getOwnPropertyDescriptor(),这两个API可以设置和查看该对象上特定属性(即直接存在于对象上而不在对象的原型链中的属性)的配置。

Object.defineProperty()

Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。

Object.defineProperty(obj, prop, descriptor)
 
/* 
	obj  要定义属性的对象。
	prop  一个字符串或Symbol,指定了要定义或修改的属性键。
	descriptor  要定义或修改的属性的描述符。
*/
 

函数返回值

传入函数的对象,其指定的属性已被添加或修改。

相比直接给对象赋值,definePorperty的区别是什么?

能更自由的配置该对象上的属性:是否可修改、是否可枚举、设置getter和setter等等。

Object.defineProperty() 允许精确地添加或修改对象上的属性。通过赋值添加的普通属性会在枚举属性时(例如 for…in、Object.keys()等)出现,它们的值可以被更改,也可以被删除。此方法允许更改这些额外细节,以使其不同于默认值。默认情况下,使用 Object.defineProperty() 添加的属性是不可写、不可枚举和不可配置的。此外,Object.defineProperty() 使用 [[DefineOwnProperty]] 内部方法,而不是 Set,因此即使属性已经存在,它也不会调用 setter。

对象中存在的属性描述符有两种主要类型:数据描述符和访问器描述符。数据描述符是一个具有可写或不可写值的属性。访问器描述符是借由 getter/setter 函数描述属性。描述符只能是这两种类型之一,不能同时为两者

数据描述符和访问器描述符都是对象。

共享的可选键值

configurable

默认值为 false

当设置为 false 时,

  • 该属性的类型不能在数据属性和访问器属性之间更改,且
  • 该属性不可被删除,且
  • 其描述符的其他属性(例如enumerable,value,writable)也不能被更改(但是,如果它是一个可写的数据描述符,则 value 可以被更改,writable 可以更改为 false)。

enumerable

默认值为 false

值为 true是,该属性可以被枚举。即可以被Object.assign()和展开运算符所考虑在内,对于非Symbol的属性,它还会在for in 和Object.keys()中显示。

数据描述符可选键值

value

默认值为 undefined

该属性的属性值。可以是任何有效的 JavaScript 值(数字、对象、函数等)。

writable

默认值为 false。此时该属性的属性值不可修改。

为 true时,该属性的属性值可以使用赋值运算符更改,就跟往常修改对象的属性值一样。

访问器描述符可选键值

get

默认值为 undefined

用作属性 getter 的函数,如果没有 getter 则为 undefined。当访问该属性时,将不带参地调用此函数,并将 this 设置为通过该属性访问的对象(因为可能存在继承关系,这可能不是定义该属性的对象)。返回值将被用作该属性的值。

set

默认值为 undefined

用作属性 setter 的函数,如果没有 setter 则为 undefined。当该属性被赋值时,将调用此函数,并带有一个参数(要赋给该属性的值),并将 this 设置为通过该属性分配的对象。

Object.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor() 静态方法返回一个对象,该对象描述给定对象上特定属性的配置。返回的对象是可变的,但对其进行更改不会影响原始属性的配置。

Object.getOwnPropertyDescriptor(obj, prop)
 
/* 
	obj  要查找其属性的对象。
	prop  要检索其描述的属性的名称或Symbol。
*/
 

函数返回值

如果指定的属性存在于对象上,则返回其属性描述符,否则返回 undefined

Vue2中的defineProperty

使用defineProperty对对象中的属性设置getter和setter,当访问此属性值时,记录下访问者(依赖收集),当属性值发生变化时,执行相应的一些函数(派发更新),达到更新视图元素的效果。

/**
 * 观察某个对象的所有属性
 * @param {Object} obj
 */
 
function observe(obj) {
  for (const key in obj) {
    let internalValue = obj[key];
    let funcs = new Set();
    Object.defineProperty(obj, key, {
      get: function () {
        //  依赖收集,记录:哪些函数依赖我
		funcs.add(window.__func);
        return internalValue;
      },
      set: function (val) {
        internalValue = val;
        // 派发更新,运行:执行那些依赖我的函数
        funcs.forEach((func) => func())
      },
    });
  }
}
 
  
// 在函数执行时记录此函数的引用
function autorun(fn) {
  window.__func = fn;
  fn();
  window.__func = null;
}

最后感谢袁老师