作者:令川 | 发布时间:2022-11-20

JS 中的属性描述符

JS 对象中的属性(prop)背后是由一个属性描述符(descriptor)来描述当前属性的行为特性,如是否可被枚举、是否可被重新赋值…

属性描述符可简称描述符,并有两种类型:数据描述符和存取描述符(也称访问器描述符)。这两种描述符也都是对象,它们有以下键值:

两种描述符共享的键值

数据描述符独有的键值

存取描述符独有的键值

描述符默认值汇总

总的来说,描述符的默认值都比较保守

查看属性的描述符

类似于: let obj = {name: 'John'}obj.age = 18 ,通过这种常用的方式创建的出来的属性都是数据属性,且描述符 configurable writable enumrable 的值都为 true

let obj = { name: 'John' }
Object.getOwnPropertyDescriptor(obj, 'name')
// -> {value: 'John', configurable: true, writable: true, enumerable: true}

obj.age = 18
Object.getOwnPropertyDescriptors(obj)
/* -> 
{
  name: {
    value: 'Jhon',
    configurable: true,
    writable: true,
    enumerable: true
  },
   age: {
    value: 18,
    configurable: true,
    writable: true,
    enumerable: true
  }
}
*/

定义属性的描述符

let obj = {}
Object.defineProperty(obj, 'name', {
  value: 'John',
  writable: false // 不可写
})
obj.name = 'Frank' // 报错!赋值运算失败

let descriptor = Object.getOwnPropertyDescriptor(obj, 'name')
descriptor.enumerable // -> false; 没有在 defineProperty() 中指明的配置项,均默认为 false
descriptor.configurable // -> false; 没有在 defineProperty() 中指明的配置项,均默认为 false
delete obj.name // 执行失败!因为 name 属性的描述符 configurable 为 false

Object.defineProperties(obj, {
  age: { value: 18; enumerable: true }, // 可枚举
  surname: { value: 'Smith', enumerable: false } // 不可枚举
})
Object.keys(obj) // -> ['age']

访问器属性由 “getter” 和 “setter” 方法表示。在对象字面量中,它们用 get  和 set  表示

let user = {
  name: 'John',
  surname: 'Smith',
}

Object.defineProperty(user, 'fullName', {
  get() {
    return `${this.name} ${this.surname}`
  },

  set(value) {
    ;[this.name, this.surname] = value.split(' ')
  },
})

user.fullName // -> 'John Smith'

user.fullName = 'Alice Cooper'
Object.values(user) // -> ['Alice', 'Cooper']

如果存取描述符只声明了 get()  方法,那么该对象属性可读不可写;如果只声明了 set()  方法,那么该对象属性可写不可读;只有同时声明了 get()  方法和 set()  方法,该属性才可读可写。

存取属性和数据属性

如果对象属性的描述符是存取描述符,那么该属性也称为存取属性;同理,如果对象属性的描述符为数据描述符,那么该属性也称为数据属性

存取属性有别于数据属性,我们可以将其视为一种“虚拟”的数据属性:

let user = {
  // 声明数据属性 name
  name: 'John',
  // 声明数据属性 surname
  surname: 'Smith',

  // 声明访问器属性 fullName
  get fullName() {
    return `${this.name} ${this.surname}`
  },
  set fullName(value) {
    ;[this.name, this.surname] = value.split(' ')
  },
}

数据属性 name surname  的读写是直接对该属性操作;而读取访问器属性 fullName  时,是通过执行 get()  方法获得返回值,写入访问器属性时,是通过执行 set() 方法,也正因为如此,我们可以在访问器属性上实现更丰富、更灵活的功能。

实现 get() set() 时,请注意堆栈溢出的问题:

// 这样定义访问器属性会导致程序堆栈溢出
let obj = {
  get prop() {
    return this.prop
  },
  set prop(val) {
    this.prop = val
  },
}

// 为访问器属性 prop 引入中间属性 _prop 即可避免堆栈溢出
let obj = {
  get prop() {
    return this._prop
  },
  set prop(val) {
    this._prop = val
  },
}

上述例子中的内部变量 _prop,在任何时候都不应在外部对其操作!

通过 set(),在用户赋值时给予适当的提示:

let user = {
  get name() {
    return this._name
  },
  set name(value) {
    if (value.length < 2) {
      alert('名字太短了,请至少输入 2 个字符 🙏')
      return
    }
    this._name = value
  },
}

JS 变量改变时,重新渲染视图:

import "./styles.css";

document.getElementById("app").innerHTML = `
<h1>Hello world</h1>
`;

目录 / Contents

空。

令川 · 记