对象属性的属性

引子

vue 的大行其道, 使得 Object.defineProperty 被更多人所认识. vue 利用 Object.defineProperty 提供的特性实现了数据绑定.

难道 Object.defineProperty 能做的只有这些么? 很显然不是.

属性描述符

The Property Descriptor type is used to explain the manipulation and reification of Object property attributes.

属性描述符到底是什么? 说白了就是对象属性的属性解释与具化, 就是对象属性本身具有哪些属性.

平时我们创建一个对象并为对象添加属性时, 可以这样

let obj = new Object()
obj.a = 'hello world'
复制代码

有了对象字面量后, 想要达到同样的效果就更加省事了. 现在也都提倡使用字面量来创建对象.

let obj = {
    a: 'hello world'
}
复制代码

所以说程序员都是懒人嘛, 怎么简单怎么来. 通过 Object.defineProperty 为对象添加属性的方式也就淡出人们的视野.

同样是为对象添加属性, 它们有什么区别呢?

其实不论是通过赋值, 还是通过字面量, 还是通过 Object.defineProperty, 最终还是殊途同归.

讲真, 平时的开发中, 使用对象字面量创建对象并添加属性时,压根就不会考虑到对象属性非个人意愿的改变了. 在我们看来, 对象就是存储着键值、键值映射用的. 我们可以任意添加, 删除, 更改对象属性, 我们认为这是理所当然的. 现实也的确如此, 你有对它为所欲为的权利.

添加属性的差异

可是为什么呢??

?你哪来的那么多为什么? 你为什么为什么呢!!!?

如果你稍微对 Object.defineProperty 有点了解, 应该知道通过这种方式定义的繁琐. 你也应该知道对象属性的操作也是有限制的. 想要放开权限, 我们需要这样做

let obj = {}
Object.defineProperty(obj, 'a', {
    value: 'hello world',
    configurable: true,
    enumerable: true,
    writable: true
})
复制代码

对象字面量添加属性只是默认都为 true. 所以我们才可以为所欲为. 不相信么? 我们可以通过 Object.getOwnPropertyDescriptor 验证一下

let obj = {}
obj.a = 'hello world'
let property = Object.getOwnPropertyDescriptor(obj, 'a')
// > property
// { value: 'hello world',
// writable: true,
// enumerable: true,
// configurable: true }
复制代码

通过 Object.getOwnPropertyDescriptor 添加的属性, 可以自由灵活地设置属性描述符. 如果我不想配置的话, 它也有自己的默认值. 需要注意的是, 这里的默认值和对象字面量添加属性与赋值属性不同, 默认值为 false.

let obj = {}
Object.defineProperty(obj, 'a', {
    value: 'hello world'
})
let property = Object.getOwnPropertyDescriptor(obj, 'a')
// > property
// { value: 'hello world',
// writable: false,
// enumerable: false,
// configurable: false }
复制代码

属性描述符等位数据描述符和访问描述符. 以上说的都是数据描述符.

传闻中的Vue优化方案

道理我都懂, 那你这句话是啥个意思?

压根就不会考虑到对象属性非个人意愿的改变了.

在 Vue 的 data选项中,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 中的访问描述符 get/set 访问描述符重新定义一遍. 再结合观察者模式, 每次属性变化时都会收到通知, 从而达到数据绑定的效果. 显然并不是所有的属性都需要被转换监听.

对于展示型的数据, 就没有必要也不会出现数据内部属性的变化, 所以没必要做以上的处理. Object.freeze (用法可见Object构造函数)处理后的对象, 就可以使得对象属性添加、修改等操作失效. 这样不去转换也不用去监听, 性能自然也就提高了.

属性描述符键值

枯燥的描述开始...

数据描述符和访问描述符同事具有的键值


configurable : 若为 false, 不能删除该属性, 不能切换属性描述符(数据描述符切到访问描述符,或访问描述符切到数据描述符), 不能更改该属性的属性(对于数据描述符来说, value 属性除外, Writable 属性从 true 置为 false 除外). 说白了就是属性描述符的开关, 管理着该属性的属性.

let obj = {}
Object.defineProperty(obj, 'a', {
    value: 'hello world',
    configurable: false,
    enumerable: true,
    writable: true
})
Object.defineProperty(obj, 'a', {
    value: 'hi world',
})
let property_1 = Object.getOwnPropertyDescriptor(obj, 'a')
Object.defineProperty(obj, 'a', {
    value: 'hi world',
    writable: false
})
let property_2 = Object.getOwnPropertyDescriptor(obj, 'a')
// > property_1
// { value: 'hi world',
// writable: true,
// enumerable: true,
// configurable: false }
// > property_2
// { value: 'hi world',
// writable: false,
// enumerable: true,
// configurable: false }

Object.defineProperty(obj, 'a', {
    value: 'hi world',
    configurable: false,
    enumerable: false
})
// TypeError
复制代码

enumerable : 若为 true, 则在 for...in 枚举中可被枚举到.

let obj = {
    a: 'hello world',
    b: 'hi world'
}
Object.defineProperty(obj, 'c', {
    value: 'hey world',
    enumerable: false
})
let propertyArray = Object.keys(obj)
// > propertyArray
// [ 'a', 'b' ]
复制代码

仅数据描述符具有的键值


value : 对象的该属性对应的值. 可以为任何有效的 JavaScript 值.

writable : 若为false, 更改 value 将会失败.

let obj = {}
Object.defineProperty(obj, 'a', {
    value: 'hello world',
    writable: false,
    configurable: true
})
obj.a = 'hi world'
let property_1 = Object.getOwnPropertyDescriptor(obj, 'a')
Object.defineProperty(obj, 'a', {
    value: 'hey world'
})
let property_2 = Object.getOwnPropertyDescriptor(obj, 'a')
// > property_1
// { value: 'hello world',
// writable: false,
// enumerable: false,
// configurable: true }
// > property_2
// { value: 'hey world',
// writable: false,
// enumerable: false,
// configurable: true }
复制代码

从运行结果可以看出, 通过赋值更改属性值时, 会更改无效.

但是通过 Object.defineProperty 更改时, 将会成功更改属性值. 对此, 规范有提到, 当可配置不可写时更改属性值,会有 Writable 置为 true, => 设置 value => Writable 置为 false.

Step 8.b allows any field of Desc to be different from the corresponding field of current if current’s [[Configurable]] field is true. This even permits changing the [[Value]] of a property whose [[Writable]] attribute is false. This is allowed because a true [[Configurable]] attribute would permit an equivalent sequence of calls where [[Writable]] is first set to true, a new [[Value]] is set, and then [[Writable]] is set to false.

不过通过赋值和 Object.defineProperty 方式修改 value, 可以看出它们内部操作还是存在着差异.

仅访问描述符具有的键值


get : 访问该属性时, 该方法都会被执行.

set : 修改该属性时, 该方法都会被执行.

vue 就是利用了访问描述符的这些特性, 实现了数据绑定.

let obj = {}
let val
Object.defineProperty(obj, 'a', {
    set(value) {
        val = value
        console.log('set => value', val)
    },
    get() {
        console.log('get => value', val)
        return val
    },
    configurable: true,
    enumerable: true
})
// > obj
// { a: [Getter/Setter] }
// > obj.a = 'hello world'
// set => value hello world
// 'hello world'
// > obj.a
// get => value hello world
// 'hello world'
复制代码

over!

你可能感兴趣的:(对象属性的属性)