一个对象的属性要么是 数据属性 ,要么是 访问器属性 (accessor properties),属性描述符(descriptor)的不同决定了属性的不同类型。

数据属性的描述符

  • configurable 若为 true ,则该属性的描述符可以被修改/被配置,该属性也可被删除
  • enumerable 若为 true ,则该属性可被枚举(可出现在诸如 for...in... Object.keys() Object.entries() 等操作中)
  • writable 若为 true ,则该属性的值可以被修改,即该属性可写
  • value 即该属性的值

    访问器属性的描述符

  • configurable

  • enumerable
  • get 一个没有参数的函数,在读取属性时自动调用,读取属性的结果为函数的返回值
  • set 一个带有一个参数的函数,在属性被赋值时自动调用,要赋的新值即为函数的参数

    查看属性的描述符

  • 通过 Object.getOwnPropertyDescriptor(obj, prop) 可以查看对象单个属性的描述符

  • 通过 Object.getOwnPropertyDescriptors(obj) 可以查看对象所有属性的描述符

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

  1. let obj = {name: 'John'}
  2. Object.getOwnPropertyDescriptor(obj, 'name')
  3. // -> {value: 'John', configurable: true, writable: true, enumerable: true}
  4. obj.age = 18
  5. Object.getOwnPropertyDescriptors(obj)
  6. /* ->
  7. {
  8. name: {
  9. value: 'Jhon',
  10. configurable: true,
  11. writable: true,
  12. enumerable: true
  13. },
  14. age: {
  15. value: 18,
  16. configurable: true,
  17. writable: true,
  18. enumerable: true
  19. }
  20. }
  21. */

定义属性的描述符

  • 通过 Object.defineProperty(obj, prop, descriptor) 可以为对象的某个属性设置描述符
  • 通过 Object.defineProperties(obj, descriptors) 可以一次为对象设置多个属性描述符

注:若使用以上两种方法定义属性的描述符,那么进行配置的配置项均为 false

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”方法表示。在对象字面量中,它们用 getset 表示

let user = {
  name: "John",
  surname: "Smith"
};

Object.defineProperty(user, 'fullName', {
  get: function() {
    return `${this.name} ${this.surname}`;
  }, // 可以简写为 get() {...}

  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 surname
  name: "John",
  surname: "Smith",
    // 声明访问器属性 fullName
  get fullName() {
    return `${this.name} ${this.surname}`;
  },
  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  }
};

数据属性 name surname 的读取、写入是直接对该属性操作;而读取访问器属性 fullName 时,是通过执行 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
    }
}
let user = {
  get name() {
    return this._name;
  },
  set name(value) {
    if (value.length < 4) {
      alert("Name is too short, need at least 4 characters");
      return;
    }
    this._name = value;
  }
};

这里我们加入了 user._name 将其作为 user.name 的内部属性,多出来这样一个内部属性,可以使我们对 user.name 的操作空间更大,留有的余地更多。
任何时候都不应该在对象外部操作内部属性