Appearance
属性描述符
什么是属性描述符
js
const obj = {
a: 1,
b: 2
}
/**
* obj 要看哪个对象
* a 要看这个对象的哪个属性
*/
const desc = Object.getOwnPropertyDescriptor(obj, 'a')
console.log(desc)sh
$ node 1.js
{ value: 1, writable: true, enumerable: true, configurable: true }- value: 值
- writable: 可写
- enumerable: 可遍历
- configurable: 属性描述符是否可以配置
修改属性描述符
js
const obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, 'a', {
value: 10,
writable: false, // 不可重写
enumerable: false // 不可遍历
})
console.log('尝试重写')
obj.a = 'abc'
console.log(obj.a)
console.log('尝试使用 for in 遍历')
for (let key in obj) {
console.log(key)
}
console.log('尝试使用 Object.keys遍历')
const keys = Object.keys(obj)
console.log('查看整个obj')
console.log(obj)
console.log('设置为不可配置描述符')
Object.defineProperty(obj, 'a', {
configurable: false
})
Object.defineProperty(obj, 'a', {
writable: true
})sh
$ node setPropDesc.js
尝试重写
10
尝试使用 for in 遍历
b
尝试使用 Object.keys遍历
查看整个obj
{ b: 2 }
设置为不可配置描述符
/.../setPropDesc.js:31
Object.defineProperty(obj, 'a', {
^
TypeError: Cannot redefine property: a让类中的属性不可写
js
class UIGoods {
constructor(g) {
this.data = g
Object.defineProperty(this, data, {
value: g,
writeable: false,
configturable: false
})
}
}
var g = new UIGoods(aGoods)
g.data = 'abc'
console.log(g.data)这样确实让 g.data = 'abc' 失效,但当项目规模大起来之后,在没有文档的情况下,这条赋值语句只是失效了,但没有任何提示,会让人以为是奇怪的 bug 出现了。这里最理想的做法是在有人试图执行赋值时,让程序有报错提示。
访问器、读取器 getter、设置器 setter
js
const obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, 'a', {
get: function() {
console.log('hello world')
return 123
},
set: function(val) {
console.log('haha')
}
})
obj.a = 3 + 2
console.log(obj.a)sh
$ node getSetProp.js
haha
hello world
123读取 obj.a 的值就是调用 get() 获取其返回值,为 obj.a 设置值就是调用 set()。
get()中不要返回自身,set()中不要为自身赋值,这两种操作都会无限递归。
使用访问器来模拟普通变量
js
const obj = {
a: 1,
b: 2
}
var internalValue = undefined
Object.defineProperty(obj, 'a', {
get: function() {
return internalValue
},
set: function(val) {
internalValue = val
}
})
obj.a = 123
console.log(obj.a)这样就能做一个只读属性了。
使用访问器来做只读属性
js
class UIGoods {
constructor(g) {
this.data = g
Object.defineProperty(this, 'data', {
get: function() {
return g
},
set: function() {
throw new Error('data 属性是只读的')
},
configurable: false
})
}
}
const aGoods = {
pic: '',
title: '..',
desc: '...',
sellNumber: 1,
favorRate: 2,
price: 3
}
var g = new UIGoods(aGoods)
console.log(g.data)
g.data = 'abc'sh
node readonlyProp.js
{
pic: '',
title: '..',
desc: '...',
sellNumber: 1,
favorRate: 2,
price: 3
}
/.../property_descriptor/readonlyProp.js:9
throw new Error('data 属性是只读的')
^
Error: data 属性是只读的访问器的语法糖
js
class UIGoods {
get data() {}
set data(v) {}
}这种语法糖有局限性,对于需要在构造函数中传入参数的,和 get、set 需要借助临时变量的,不能使用这种语法糖。
对象冻结
如果想要修改对象内的属性
js
g.data.price = 0上面的 setter 是拦不住的,因为修改的是 g.data 内部的属性。这时可以冻结整个 g。
js
...
constructor(g) {
Object.freeze(g)
}
...但由于对象是引用,这会导致原始对象 aGoods 也冻结。通常的做法是克隆一个对象出来,对克隆的对象进行冻结。
js
...
constructor(g) {
g = { ...g }
Object.freeze(g)
}
...还有的时候会有人向生成后的对象中添加属性,例如
js
var g = new UIGoods(aGoods)
g.abc = 123这时候可以在构造函数的末尾对当前对象进行冻结。
js
...
constructor(g) {
g = { ...g }
// ... 其它 Object.defineProperty 代码
Object.freeze(g)
}
...对象密封
使用冻结后会导致本来写好的 setter 都不能用了,这时候可以使用 seal 替代 freeze。这样本来写好的 getter 和 setter 都可以用,但不能修改原对象,也不能向原对象里添加属性。
js
...
constructor(g) {
g = { ...g }
// ... 其它 Object.defineProperty 代码
Object.seal(g)
}
...原型冻结
冻结对象后防不住直接向原型上添加属性。
js
UIGoods.prototype.haha = 'abc'这时候可以冻结原型
js
Object.freeze(UIGoods.prototype)