JS属性特性(属性描述符)

概念 ECMAScript 5 中定义了一个名叫“属性描述符”的对象,用于描述了的各种特征。属性描述符对象有4个属性:

概念

ECMAScript 5 中定义了一个名叫“属性描述符”的对象,用于描述了的各种特征。属性描述符对象有4个属性:

  • configurable:可配置性,控制着其描述的属性的修改,表示能否修改属性的特性,能否把属性修改为访问器属性,或者能否通过 delete删除属性从而重新定义属性。默认值为 true

  • enumerable:可枚举性,表示能否通过 for-in遍历得到属性。默认值为 true

  • writable:可写性,表示能否修改属性的值。默认值为 true

  • value数据属性,表示属性的值。默认值为 undefined

除了上面的属性,还有两个 存取器属性,分别是 getset,可以代替 valuewritable

  • get:在读取属性时调用的函数。只指定 get则表示属性为只读属性。默认值为 undefined

  • set:在写入属性时调用的函数。只指定 set则表示属性为只写属性。默认值为 undefined

使用

“属性描述符”对象只能在 Object.definePropertyObject.defineProperties中使用。

API 用法

Object.defineProperty: https://developer.mozilla.org...

Object.defineProperties: https://developer.mozilla.org...

var hello = {}Object.defineProperty(hello, 'girl', { configurable: false, enumberable: false, writable: true, value: 'sexy'})// 存取器Object.defineProperty(hello, 'woman', { configurable: false, enumberable: false, get: function() { return this.girl }, set: function(val) { this.girl = val }})// 定义多个属性Object.defineProperties(hello, { boy: { configurable: false, enumberable: false, writable: false, value: 'handsome' }, man: { configurable: false, enumberable: false, writable: true, get: function() { return this.boy } }})

当用 Object.definePropertyObject.defineProperties操作(新建或者修改)那些不允许创建或修改的属性时,会抛出类型错误异常。

// 此例子运行在前面的例子的基础上Object.defineProperty(hello, 'boy', { writable: true}) // Uncaught TypeError: Cannot redefine property: boy

因为前面 boy属性已经被设置为不可配置,所以这里修改 writable会抛出类型错误异常。

通过 Object.getOwnPropertyDescriptor或者 Object.getOwnPropertyDescriptors可以得到属性描述符。

API 用法

Object.getOwnPropertyDscriptor: https://developer.mozilla.org...

Object.getOwnPropertyDescriptors: https://developer.mozilla.org...

规则

var rules = { common: 'test'}

如果属性是不可配置的,则不能修改它的可配置性和可枚举性。

Object.defineProperty(rules, 'rule1', { configurable: false, enumberable: false})// 修改configurable会抛出类型错误异常Object.defineProperty(rules, 'rule1', { configurable: true}) // Uncaught TypeError: Cannot redefine property: rule1// 修改enumberable不会抛出异常,但enmuberable没有被修改Object.defineProperty(rules, 'rule1', { enumberable: true})Object.getOwnPropertyDescriptor(rules, 'rule1') // Object {value: undefined, writable: false, enumerable: false, configurable: false}

如果存取器属性是不可配置的,则不能修改 getset方法,也不能将它转换为数据属性。

Object.defineProperty(rules, 'rule2', { configurable: false, enumberable: false, get: function() { return this.common }, set: function(val) { this.common = val }})// 修改get或者set方法会抛出类型错误异常Object.defineProperty(rules, 'rule2', { get: function() { return this.common + 'rule2' }}) // Uncaught TypeError: Cannot redefine property: rule2Object.defineProperty(rules, 'rule2', { set: function(val) { this.common = 'rule2' }}) // Uncaught TypeError: Cannot redefine property: rule2// 将它转换为数据属性同样会抛出类型错误异常Object.defineProperty(rules, 'rule2', { value: 'rule2'}) // Uncaught TypeError: Cannot redefine property: rule2

如果数据属性是不可配置的,则不能将它转换为存取器属性;同时,也不能将它的可写性从 false修改为 true,但可以从 true修改为 false

Object.defineProperty(rules, 'rule3', { configurable: false, writable: false, value: 'rule3'})// 修改writable为true会抛出类型错误异常Object.defineProperty(rules, 'rule3', { writable: true})Object.defineProperty(rules, 'rule4', { configurable: false, writable: true, value: 'rule4'})// 可以修改writable为falseObject.defineProperty(rules, 'rule4', { writable: false})Object.getOwnPropertyDescriptor(rules, 'rule4') // Object {value: "rule4", writable: false, enumerable: false, configurable: false}

如果数据属性是不可配置且不可写的,则不能修改他的值;如果是可配置但不可写,则可以修改他的值(实际上是先将它标记为可写的,然后修改它的值,最后再将它标记回不可写)。

其实这里所说的修改值,是通过 Object.definePropertyObject.defineProperties方法修改。通过直接赋值的方法在数据属性不可配置的情况下是不能修改属性值的。

Object.defineProperty(rules, 'rule5', { configurable: false, writable: false, value: 'rule5'})// 修改属性值会抛出类型错误异常Object.defineProperty(rules, 'rule5', { value: 'rule55'}) // Uncaught TypeError: Cannot redefine property: rule5rules.rule5 = 'rule55'// 值没有被修改,也不会抛出异常rules.rule5 // 'rule5'Object.defineProperty(rules, 'rule6', { configurable: true, writable: false, value: 'rule6'})// 修改属性值Object.defineProperty(rules, 'rule6', { value: 'rule66'})rules.rule6 // 'rule66'rules.rule6 = 'rule6'// 值没有被修改,也不会修改rules.rule6 // 'rule6'

只指定 get不能写,如果尝试对该属性赋值,会抛出类型错误异常。(红宝书上说只有在严格模式下才抛出异常)

Object.defineProperty(rules, 'rule7', { get: function() { return this.common }})rules.rule7 = 'rule7' // Uncaught TypeError: Cannot redefine property: rule7

只指定 set不能读,如果尝试读取该属性值,返回undefined。(红宝书上说在严格模式下才抛出异常,但没有)

Object.defineProperty(rules, 'rule8', { set: function() { this.common = 'rule8' }})rules.rule8 // undefined

如果对象是不可扩展的,则可以编辑已有的自有属性,但不能给它添加新属性。

操作对象可扩展性的API有三个: Object.preventExtensionsObject.sealObject.freeze

API 用法

Object.preventExtensions: https://developer.mozilla.org...

Object.seal: https://developer.mozilla.org...

Object.freeze: https://developer.mozilla.org...

Object.isExtensions: https://developer.mozilla.org...

Object.isSealed: https://developer.mozilla.org...

Object.isFrozen: https://developer.mozilla.org...

使用 Object.preventExtensions可以将对象转换为不可扩展。

使用 Object.isExtensions来判断对象是否可扩展。

var ex = {}Object.defineProperty(ex, 'ex1', { configurable: true, writable: true, value: 'ex1'})Object.isExtensible(ex) // trueObject.preventExtensions(ex)Object.isExtensible(ex) // false// 可以修改已有的属性Object.defineProperty(ex, 'ex1', { writable: false, value: 'ex11'})Object.getOwnPropertyDescriptor(ex, 'ex1') // Object {value: "ex11", writable: false, enumerable: false, configurable: true}// 添加属性会抛出类型错误异常Object.defineProperty(ex, 'ex2', { value: 'ex2'}) // Uncaught TypeError: Cannot define property:ex2, object is not extensible.

使用 Object.seal除了可以将对象转换为不可扩展的,还可以将对象的所有自有属性都转换为不可配置的。即不能给对象添加新属性,而且它已有的属性也不能删除或者配置(这里同样会遵循前面的规则)。

使用 Object.isSealed来判断对象是否封闭(sealed)。

var se = {}Object.defineProperty(se, 'se1', { configurable: true, writable: false, value: 'se1'})Object.isSealed(se) // falseObject.seal(se)Object.isSealed(se) // true// 修改已有的属性会抛出类型错误异常Object.defineProperty(se, 'se1', { writable: true, value: 'se11'}) // Uncaught TypeError: Cannot redefine property: se1// 添加属性会抛出类型错误异常Object.defineProperty(se, 'se2', { value: 'se2'}) // Uncaught TypeError: Cannot define property:se2, object is not extensible.

使用 Object.freeze除了将对象转换为不可扩展的和将其属性转换为不可配置的之外,还可以将自有属性转换为只读。(如果对象设置了 set,存取器属性将不会受影响,仍可以调用 set方法,而且不会抛出异常,但如果 set方法是改变该对象的属性,则不能修改成功)

使用 Object.isFrozen来检测对象是否冻结(frozen)。

var fr = {}Object.defineProperty(fr, 'fr1', { configurable: true, writable: false, value: 'fr1'})Object.isFrozen(fr) // falseObject.freeze(fr)Object.isFrozen(fr) // true// 修改已有的属性会抛出类型错误异常Object.defineProperty(fr, 'fr1', { writable: true, value: 'fr11'}) // Uncaught TypeError: Cannot redefine property: fr1// 添加属性会抛出类型错误异常Object.defineProperty(fr, 'fr2', { value: 'fr2'}) // Uncaught TypeError: Cannot define property:fr2, object is not extensible.fr.fr1 = 'fr11'// 不能修fr1属性fr.fr1 // 'fr1'
var set = {}Object.defineProperty(set, 'set1', { configurable: true, value: 'set1'})Object.defineProperty(set, 'set2', { configurable: true, set: function(val) { this.set1 = val }})Object.isFrozen(set) // falseObject.freeze(set)Object.isFrozen(set) // trueset.set2 = 'set2'set.set1 // 'set1'

结语

我对属性描述符很不熟悉,主要是因为平时用得少。不过最近,开始学写一些小的库(虽然很挫),就感觉属性描述符有使用的场景了。我暂时能想到的就是将库对象的一些属性设置为只读,以防止对象的一些属性被用户重写覆盖了。还有一个用法是在知乎和学vue的时候知道的,就是通过 gettersetter实现“监听”对象属性的数据更新(在这里挖一个坑。后面学习一下这种方法,再写一篇“监听”对象属性的数据更新的文章)。

最后,如果大家知道更多属性描述符的使用后场景,希望大家能在评论区留下你们的高见。

未登录用户
全部评论0
到底啦