JavaScript对象属性
1.属性类型与数据描述符
在Javascript
中,对象的属性有两种类型:
- 数据属性Data property
- 访问器属性Accessor property
属性的类型取决于在定义时,传入的属性描述符对象。
1.1 数据属性的属性描述符
在通过Object.defineProperty()
定义数据属性时,它独有的属性描述符有两个:
value
: 属性的值,默认为undefined
writable
: 是否可写(修改),默认为false
javascript
const obj = {}
Object.defineProperty(obj, 'a', {})
console.log(obj.a) // undefined
obj.a = 'Hello JavaScript'
console.log(obj.a) // undefined
在上面的代码中,给obj
对象定义了一个名为a
的属性,提供了一个空的属性描述符对象,所以,value
和writable
都取默认值。 因此,value
为undefined
,writable
为false
。这就导致了无法修改obj.a
的值,所以,第二次也是输出undefined
。
这里没有使用严格模式,修改obj.a
的值失败时,不会收到任何警告提示,在写代码时,需要特别注意。 如果想要在读取只读属性时报错,则需要使用严格模式。(在文件开头添加"use strict"
)
在非严格模式下,如果想要让一个属性只读,在修改只读属性时要给出一定的提示,可以使用另一种类型的属性,即访问器属性。
1.2 访问器属性的属性描述符
在通过Object.defineProperty()
定义访问器属性时,它独有的属性描述符有两个:
get()
:访问器getter,在访问属性的时候调用set()
:设置器setter,在设置属性的时候调用
javascript
let data = 'Hello World'
const obj = {}
Object.defineProperty(obj, 'a', {
get() {
return data
},
set(value) {
data = value
}
})
console.log(obj.a) // Hello world
obj.a = 'Hello JavaScript'
console.log(obj.a) // Hello JavaScript
console.log(obj.a == data) // true
上面的代码中,使用defineProperty()
定义了一个访问器属性,当使用obj.a
获取属性的值时,就会调用getter ; 当obj.a
出现在赋值号左边时,就会调用setter ,且把赋值号右边的表达式的值作为参数传递给setter。
现在,回过头想一下上面的问题,在非严格模式下,对于writable: false
的数据属性,在修改它的值时,没有任何的提示,现在,通过访问器属性,我们可以解决这个问题:
javascript
let data = 'Hello World'
const obj = {}
Object.defineProperty(obj, 'a', {
get() {
return data
},
set(value) {
console.warn('The property a of obj is readonly')
}
})
obj.a = 'Hello JavaScript' // WARN: The property a of obj is readonly
对于上面的代码 ,也不是说obj.a
绝对不可变,只是说我们无法通过obj.a = ...
的方式修改。 换句话说,我们创建了一个变量data
的代理,我们没办法通过代理修改data
,但是还是可以直接修改data
。
javascript
let data = 'Hello World'
const obj = {}
Object.defineProperty(obj, 'a', {
get() {
return data
},
set(value) {
console.warn('The property a of obj is readonly')
}
})
data = 'Hello JavaScript'
console.log(obj.a) // Hello JavaScript
1.3 通用的属性描述符
上面两小节,分别介绍了两种不同类型属性各自特有的属性描述符。 下面,介绍这两种属性共有的描述符。在通过Object.defineProperty()
定义属性时,两种属性共有的属性描述符有两个:
configurable
:是否可配置。默认为false
enumerable
: 是否可枚举。默认为false
configurable
如果configurable
为false
:
- 无法修改属性的类型,即不能将数据属性重新定义为访问器属性,反之亦然。
- 对于访问器属性,无法重新定义
get()
和set()
函数。 - 对于数据属性:
- 如果
writable
为true
,则可以修改value
,这符合writable
的语义。 - 如果
writable
为true
,则可以将writable
修改为false
,反过来则不行。
- 如果
- 无法删除属性
数据属性举例:
javascript
const obj = {}
Object.defineProperty(obj, 'a', { value: 'Hello World', writable: true /**configurable默认为false */})
// 尝试修改value
Object.defineProperty(obj, 'a', { value: 'Hello JavaScript' })
console.log(obj.a) // Hello JavaScript
// 尝试修改writable
Object.defineProperty(obj, 'a', { writable: false })
obj.a = '666' // writable是false 修改失败 没有提示信息
console.log(obj.a) // Hello JavaScript
// 尝试删除属性
delete obj.a // 删除失败 没有提示信息
console.log(obj.a) // Hello JavaScript
// 尝试修改writable
Object.defineProperty(obj, 'a', { writable: true }) // TypeError: Cannot redefine property: a
// configurable为false 不能将writable从false 改成true
// 尝试修改属性类型
Object.defineProperty(obj, 'a', { get() { return 'Hello JavaScript' } }) // TypeError: Cannot redefine property: a
// configurable为false 不能改变属性类型
// 尝试删除属性
delete obj.a
访问器属性举例:
javascript
const obj = {}
Object.defineProperty(obj, 'a', { get() { return 'Hello World' } })
// 尝试修改getter
Object.defineProperty(obj, 'a', { get() { return 'Hello JavaScript' } }) // TypeError: Cannot redefine property: a
// 尝试修改属性类型
Object.defineProperty(obj, 'a', { value: 'Hello JavaScript' }) // TypeError: Cannot redefine property: a
enumerable
如果enumerable
为false
:
Object.assign()
函数会忽略该属性。- 展开语法会忽略该属性。
for...in
循环会忽略该属性。Object.keys()
会忽略该属性。
javascript
const obj = {}
Object.defineProperty(obj, 'a', { value: 1 /**enumerable默认为false */})
Object.defineProperty(obj, 'b', { value: 2, enumerable: 'true' })
console.log(Object.assign({}, obj)) // {b: 2}
console.log({...obj}) // {b: 2}
for(const key in obj) {
console.log(key)
}
// b
console.log(Object.keys(obj))// ['b']
1.4 小结
在上面我介绍描述符的默认值时候,我都会强调在使用Object.defineProperty()
函数时 。 别忘了,在一开始学习JavaScript
时,我们使用更简单的方式定义属性:
javascript
const obj = {}
obj.a = 'Hello World'
在使用.
运算符定义属性时,会定义一个数据属性,且configurable
,enumerable
,writable
均为true
javascript
const obj = {}
obj.a = 'Hello World'
console.log(Object.getOwnPropertyDescriptor(obj, 'a')) // {"value":"Hello World","writable":true,"enumerable":true,"configurable":true}
还有另一个细节,对于已经存在 的数据属性来说,如果通过Object.defineProperty()
修改value
,会调用内部方法[[DefineOwnProperty]]
; 然而,如果使用obj.pro = ...
的方式修改属性的值,会调用内部方法[[Set]]
。
我们可以通过代理拦截内部方法
javascript
const obj = {}
const proxy = new Proxy(obj, {
set(target, key, newValue) {
console.log('[[Set]]')
return Reflect.set(target, key, newValue)
},
defineProperty(target, key, descriptor) {
console.log('[[DefineProperty]]')
return Reflect.defineProperty(target, key, descriptor)
}
})
Object.defineProperty(proxy, 'a', { value: 'Hello World', writable: true }) // [[DefineProperty]]
proxy.a = '-> Hello JavaScript' // [[Set]]
Object.defineProperty(proxy, 'b', { value: 'Hello Proxy' }) // [[DefineProperty]]
Object.defineProperty(proxy, 'b', { value: 'Hello new value' }) // [[DefineProperty]]
最后,推荐使用严格模式,否则很多错误信息会被忽略。
2.其他属性相关函数
2.1 阻止添加新属性 Object.preventExtensions()
这个方法可以将一个对象变成一个不可扩展的对象,即,不可添加新属性(但可以删除)。
javascript
"use strict"
const obj = {}
console.log(Object.isExtensible(obj)) // true
Object.preventExtensions(obj)
console.log(Object.isExtensible(obj)) // false
obj.a = 'Hello world' // TypeError: Cannot add property a, object is not extensible
虽然无法在对象本身添加属性,但可以在原型对象上添加属性,实现看起来像添加了新属性的效果。
javascript
"use strict"
const proto = {}
const obj = Object.create(proto)
Object.preventExtensions(obj)
proto.a = 'Hello world'
console.log(obj.a) // Hello world
虽然不可扩展对象可以修改原型对象的内容,但不可以更改指向原型对象的引用。
javascript
"use strict"
const proto = {}
const obj = {}
Object.preventExtensions(obj)
Object.setPrototypeOf(obj, proto) // TypeError: #<Object> is not extensible
2.2 Object.seal()
Object.seal()
的行为:
- 将对象变标记为不可扩展(
Object.isExtensible()
返回false
) - 将所有属性的属性描述符
configurable
修改为false
javascript
"use strict"
const obj = {}
Object.defineProperty(obj, 'a', { value: 'Hello World' })
console.log(Object.isExtensible(obj)) // true
console.log(Object.issealed(obj)) // false
Object.seal(obj)
console.log(Object.isExtensible(obj)) // false
console.log(Object.issealed(obj)) // true
Object.defineProperty(obj, 'a', { writable: true }) // TypeError: Cannot redefine property: a
2.3 Object.freeze()
Object.freeze()
的行为:
Object.seal()
- 将所有数据属性的
writable
设置为false
javascript
"use strict"
const obj = {}
Object.defineProperty(obj, 'a', { value: 'Hello World', writable: true })
console.log(Object.isExtensible(obj)) // true
console.log(Object.isSealed(obj)) // false
console.log(Object.isFrozen(obj)) // false
Object.freeze(obj)
console.log(Object.isExtensible(obj)) // false
console.log(Object.isSealed(obj)) // true
console.log(Object.isFrozen(obj)) // true
obj.a = 'Hello JavaScript'