本篇文章总结控制对象访问的相关 API。
Object.defineProperty
Object.defineProperty 可以对对象的属性进行配置,它接收如下参数:
obj:对象prop:属性descriptor:属性描述符
其中 descriptor 包括如下配置选项
value:属性的值writable:属性是否可写,默认为falseset:为属性设置值时会触发此方法,设置的新值会作为参数传入get:访问此属性是会触发此方法configurable:该属性是否可再次进行配置,默认为falseenumerable:是否可遍历,默认为false
其中 value 与 writable 是一组,set 和 get 是一组,二者不可同时使用。
例子1
const obj = {}
Object.defineProperty(obj, 'name', {
value: 'Alice',
writable: false
})
console.log(obj.name) // Alice
obj.name = 'Bob' // 不可写,更改无效
console.log(obj.name) // Alice
在上面我们为 obj 的 name 属性配置了 value 属性为 Alice 作为 obj.name 的初始值,又配置了 writable 为 false,表明该属性是不可写的,对该属性进行赋值时无效的。
例子2
const obj = {}
let username
Object.defineProperty(obj, 'name', {
set(value) {
if (value === 'Alice') {
username = value
}
},
get() {
return username
}
})
obj.name = 'Alice' // 赋值有效
console.log(obj.name) // Alice
obj.name = 'Bob' // 赋值无效
console.log(obj.name) // Alice
上面我们为 obj 的 name 属性配置了 set 和 get 方法,没当我们为 obj.name 赋予新值时都会调用 set 方法,并将新值传入,在 set 方法中,我们对新值进行了判断,如果新值为 Alice 则我们赋值给 username;每次当我们访问 obj.name 时,则会触发 get 方法,在 get 方法中我们直接返回了 username 这个变量的值,通过这种方式可以控制对象属性的访问操作。
例子3
const obj = {
a: 1,
b: 2
}
console.log(Object.keys(obj)) // [ 'a', 'b' ]
Object.defineProperty(obj, 'a', {
enumerable: false // 不可枚举
})
console.log(Object.keys(obj)) // [ 'b' ]
在上面我们定义了一个 obj 对象,其中有 a, b 两个属性,默认都是可枚举的。通过 Object.keys 方法可以取得对象中所以可枚举的属性,这时它们都在返回的数组中;接着我们配置 a 为不可枚举,此时再次调用 Object.keys,此时 a 就不在返回的数组中了。
例子4
const obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, 'a', {
enumerable: false,
configurable: false
})
console.log(Object.keys(obj)) // [ 'b' ]
// TypeError: Cannot redefine property: a
Object.defineProperty(obj, 'a', {
enumerable: true
})
上面我们配置了 a 为不可枚举,并且配置 configurable 为 false,然后接着我们又想配置属性 a 为可枚举的,但是会报错 TypeError: Cannot redefine property: a,这是因为我们配置 configurable 为 false,表示不可再次配置,当我们再次对属性 a 进行配置便会报错。
const obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, 'a', {
enumerable: false,
configurable: true // 可继续配置
})
console.log(Object.keys(obj)) // [ 'b' ]
Object.defineProperty(obj, 'a', {
enumerable: true
})
console.log(Object.keys(obj)) // [ 'a', 'b' ]
Object.defineProperties
Object.defineProperty 每次只能配置对象的一个属性,而 Object.defineProperties 可以一次性配置对象的多个属性,它的语法如下:
Object.defineProperties(obj, {
prop1: {
// 配置选项同 Object.defineProperty
},
prop2: {
// ...
}
})
见下例:
const obj = {
name: 'Alice',
age: 25
}
Object.defineProperties(obj, {
name: {
writable: false // 不能写 name 属性
},
age: {
enumerable: false, // 不能遍历到 age 这个属性
get() {
return 18 // 别问,问就是 18
}
}
})
Object.preventExtensions
Object.preventExtions(obj) 可以使得对象 obj 不可扩展,即不能为对象添加新的属性
const obj = {
name: 'Alice',
age: 20
}
Object.preventExtensions(obj)
obj.gender = 'female' // 无法添加新的属性
console.log(obj.gender) // undefined
上面我们将 obj 设置为不可扩展后试图添加新的属性,但是并没有成功。设置为不可扩展后,我们可以正常访问现有对象上的属性,以及更改和删除
const obj = {
name: 'Alice',
age: 20
}
Object.preventExtensions(obj)
obj.name = 'Bob'
console.log(obj.name) // Bob
delete obj.name
console.log(Object.keys(obj)) // [ 'age' ]
对象一旦变为不可扩展后,就一直是不可扩展的。通过 Object.isExtensible 方法可以判断一个对象是否是可扩展的
const obj = {
name: 'Alice',
age: 20
}
console.log(Object.isExtensible(obj)) // true
Object.preventExtensions(obj)
console.log(Object.isExtensible(obj)) // false
Object.seal
seal 的意思是封印,Object.seal 比 Object.preventExtensiable 更近一步,被 seal 了的对象,除了变为不可扩展,还不可配置 configurable: false,属性不能被删除了以及属性不能被 Object.defineProperty 进行配置了
const obj = {
name: 'Alice',
age: 20
}
Object.seal(obj)
obj.gender = 'female'
console.log(obj.female) // undefined, 不可扩展
delete obj.name
console.log(obj.name) // Alice, 不可删除
但是我们还可以对象现有的属性进行更改的
const obj = {
name: 'Alice',
age: 20
}
Object.seal(obj)
obj.name = 'Bob'
console.log(obj.name) // Bob
通过 Object.isSealed 可以判断对象是否被 seal 了
const obj = {}
console.log(Object.isSealed(obj)) // false
Object.seal(obj)
console.log(Object.isSealed(obj)) // true
Object.freeze
freeze 是冻结的意思,它相对于 Object.seal 更进一步,不仅不可扩展,不可配置,对象的数据属性 writable 会被设置为 false
const obj = {
name: 'Alice'
}
Object.freeze(obj)
obj.age = 18 // 不可扩展,无效
obj.name = 'Bob' // 不可修改,无效
delete obj.name // 不可配置,无效
console.log(obj) // { name: 'Alice' }
但是如果有定义 set 方法的话,对象属性还是可以被修改的
const obj = {
name: 'Alice'
}
let username = obj.name
Object.defineProperty(obj, 'name', {
set(value) {
username = value
},
get() {
return username
}
})
Object.freeze(obj)
obj.name = 'Bob'
console.log(obj.name) // Bob
通过 Object.isFrozen 方法可以判断一个对象是否被 freeze 了
const obj = {}
console.log(Object.isFrozen(obj)) // false
Object.freeze(obj)
console.log(Object.isFrozen(obj)) // true
Proxy
Proxy 是 ES6 中的新特性,它可以为对象做一层代理,当用户通过代理访问对象时,代理可以拦截一些操作,例如为属性赋值,访问属性的值,从而控制对象的访问。Proxy 可以拦截 13 种操作,这里不会详细介绍,只会介绍 set 和 get 拦截。
Proxy 的语法如下
const proxy = new Proxy(target, {
// 拦截的方法
})
例子1
const target = {
name: 'Alice',
age: 18
}
let proxy = new Proxy(target, {
set(trapTarget, key, value, receiver) {
// 只能为 age 属性赋值,并且值必须不大于 18 才能赋值成功
if (key === 'age' && value <= 18) {
trapTarget[key] = value
}
}
})
// 无法为 name 赋值
proxy.name = 'Bob'
console.log(proxy.name) // Alice
console.log(target.name) // Alice
// 大于 18 赋值失败
proxy.age = 19
console.log(proxy.age) // 18
console.log(target.age) // 18
// 不大于 18,赋值成功
proxy.age = 15
console.log(proxy.age) // 15
console.log(target.age) // 15
上面我们为 target 对象做了层代理,当我们通过代理赋值时,会被 set 方法拦截,set 方法接收四个参数:
trapTarget:被代理的对象key:被赋值的属性value:被赋的值receiver:操作发生的对象,即代理对象
上面我们只允许对 age 属性赋值,并且赋的值不能大于 18。
例子2
const target = {
name: 'Alice',
age: 20
}
let proxy = new Proxy(target, {
get(trapTarget, key, receiver) {
if (key === 'age') {
return 18
}
return trapTarget[key]
}
})
console.log(proxy.age) // 18
console.log(target.age) // 20
我们对 target 的 get 方法进行了代理,当我们通过代理对象访问属性值时,如果访问的是 age 属性,则返回 18,其他属性不做处理直接返回原值。
如果直接通过 target 访问属性的话,操作是不会被拦截的,代理没有作用。
