Skip to content

属性描述符

什么是属性描述符

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)

最后更新于: