# JavaScript 属性描述符

# 前言
此文总结了属性描述符的作用和特性,以及限制对象操作的部分方法。
# Object.defineProperty
;Object.defineProperty (opens new window) 用于指定对象属性的描述符。
函数的第三个参数descriptor为属性的描述符,包括数据描述符和存取描述符两种。
var object = {}
// 数据描述符
Object.defineProperty(object, 'foo', {
configurable: true,
enumerable: true,
writable: true,
value: 1,
})
// 存取描述符
Object.defineProperty(object, 'bar', {
configurable: true,
enumerable: true,
get() {},
set() {},
})
注意属性描述符固定包括configurable、enumerable、writable和value四个键,存取描述符固定包括configurable、enumerable、get和set四个键。
描述符中公共键为
configurable和enumerable,数据描述符的writable与value键成对,存取描述符的get和set键成对
# 默认键值
属性描述符的键是有默认值的,但是由于对象属性的指定方式不同,存在差异。
Object.getOwnPropertyDescriptors (opens new window) 用于获取对象自身所有属性的描述符
字面量方式。
var object = {
foo: 1,
get bar() {},
}
Object.getOwnPropertyDescriptors(object)
// {
// bar: {
// configurable: true,
// enumerable: true,
// get: f bar(),
// set: undefined,
// },
// foo: {
// configurable: true,
// enumerable: true,
// value: 1,
// writable: true,
// },
// }
;defineProperty方式。
var object = {}
Object.defineProperty(object, 'foo', {
value: 1,
})
Object.defineProperty(object, 'bar', {
get() {},
})
Object.defineProperty(object, 'baz', {})
Object.getOwnPropertyDescriptors(object)
// {
// bar: {
// configurable: false,
// enumerable: false,
// get: f bar(),
// set: undefined,
// },
// baz: {
// configurable: false,
// enumerable: false,
// value: undefined,
// writable: false,
// },
// foo: {
// configurable: false,
// enumerable: false,
// value: 1,
// writable: false,
// },
// }
比较发现,字面量方式指定的属性的描述符默认键值都为true。
而defineProperty方式则相对为false,很好理解,defineProperty旨在细化地描述属性,即指定什么就是什么,未指定的当然为false。
另外defineProperty指定为空描述符时,默认为数据描述符形式。存取描述符中的get和set指定了才会有,不指定默认为undefined。
# configurable
是否可以删除此属性。
注意非严格模式下删除静默失败,严格模式将抛出错误。
'use strict'
var object = {}
Object.defineProperty(object, 'foo', {
value: 1,
configurable: false,
})
delete object.foo // Uncaught TypeError: Cannot delete property 'foo' of #<Object>
object // {foo: 1}
是否可以修改描述符的键值,例如修改enumerable。
var object = {}
Object.defineProperty(object, 'foo', {
configurable: false,
enumerable: true,
value: 1,
})
Object.defineProperty(object, 'foo', {
enumerable: false, // Uncaught TypeError: Cannot redefine property: foo at Function.defineProperty (<anonymous>)
})
注意有两个特例,value和writable。
;value键的值可以修改。
var object = {}
Object.defineProperty(object, 'foo', {
configurable: false,
writable: true,
value: 1,
})
Object.defineProperty(object, 'foo', {
value: 12,
})
object // {foo: 12}
;writable键的值只能由true修改为false。
var object = {}
Object.defineProperty(object, 'foo', {
configurable: false,
writable: true,
value: 1,
})
Object.defineProperty(object, 'foo', {
writable: false,
})
object // {foo: 1}
但是不能由false修改为true。
var object = {}
Object.defineProperty(object, 'foo', {
configurable: false,
writable: false,
value: 1,
})
Object.defineProperty(object, 'foo', {
writable: true, // Uncaught TypeError: Cannot redefine property: foo at Function.defineProperty (<anonymous>)
})
所以configurable用于表示属性是否可删除,描述符的键是否可修改。
当configurable为false时,属性不能被删除。除了writable和value之外的键(包括configurable、enumerable、set和get)不能修改,注意writable也只能修改为false。
# enumerable
是否可以枚举此属性。
var object = { foo: 1 }
Object.defineProperty(object, 'bar', {
enumerable: false,
value: 2,
})
for (var prop in object) {
console.log(prop) // foo
}
Object.keys(object) // ['foo']
JSON.stringify(object) // {"foo":1}
Object.assign({}, object) // {foo: 1}
object // {foo: 1, bar: 2}
Object.prototype.propertyIsEnumerable (opens new window) 可用于判断对象的属性值是否可枚举
几种遍历对象的方式。
for...in:遍历对象自身和继承的可枚举属性,不包括Symbol属性Objects.keys / Objects.values / Objects.entries:返回对象自身的键或值组成的数组,也是可枚举属性,不包括Symbol键Object.getOwnPropertyNames:返回对象自身的属性,不包括Symbol属性Object.getOwnPropertySymbols:返回对象自身的Symbol属性Reflect.ownKeys:返回对象自身的所有属性
# writable
是否可以修改此属性。
注意非严格模式下修改静默失败,严格模式将抛出错误。
'use strict'
var object = {}
Object.defineProperty(object, 'foo', {
writable: false,
value: 1,
})
object.foo = 2 // Uncaught TypeError: Cannot assign to read only property 'foo' of object '#<Object>'
object // {foo: 1}
;configurable为true时,可以先删除,再添加属性,达到修改属性的目的。
'use strict'
var object = {}
Object.defineProperty(object, 'foo', {
configurable: true,
writable: false,
value: 1,
})
delete object.foo
object.foo = 2
object // {foo: 2}
# value
指定属性值,默认为undefined。
var object = {}
Object.defineProperty(object, 'foo', {})
object // {foo: undefined}
# get
访问器getter,在访问对象属性时触发。
var object = {}
Object.defineProperty(object, 'foo', {
get() {
console.log('get')
},
})
object.foo // get
特别注意get函数内this会静默绑定。
var object = {}
Object.defineProperty(object, 'foo', {
get() {
console.log(this === object) // true
},
})
object.foo
观察发现,点运算符(.)左边的对象是谁,this就指向谁。
var object = {}
Object.defineProperty(object, 'foo', {
get() {
console.log(this === o) // true
},
})
var o = Object.create(object)
o.foo
换句话说this指向触发读取操作时的对象。
# set
访问器setter,在修改对象属性值时触发。
var object = {}
Object.defineProperty(object, 'foo', {
set() {
console.log('set')
},
})
object.foo = 1 // set
类似的,set函数内this指向触发修改操作时的对象。
# 小结
- 描述符分为数据和存取两种类型,数据类型包括
configurable、enumerable、writable和value,存取类型包括configurable、enumerable、get和set - 字面量指定的属性描述符的默认键值为
true,defineProperty为false - 若属性的
configurable键值为false,不能删除此属性,也不能修改除了writable和value之外的键,且writable也只能修改为false - 若属性的
enumerable为false,不可枚举此属性,或者说会被忽略。例如for...in、Object.keys、JSON.stringify和Object.assign - 若属性的
writable为false,不可修改此属性 - 访问器属性有
get和set,在访问对象属性时触发get,在修改对象属性值时触发set,两者内部都会静默绑定this,指向触发操作时的对象
# 对象
除了属性描述符可以限制对象的操作之外,Object上也有方法限制对象的操作。
# Object.preventExtensions
;Object.preventExtensions (opens new window) 表示让对象不可拓展。
不能添加新的属性。
注意非严格模式下添加属性静默失败,严格模式将抛出错误。
'use strict'
var object = {}
Object.preventExtensions(object)
object.foo = 1 // Uncaught TypeError: Cannot add property foo, object is not extensible
object // {}
另外对象的内部槽[[prototype]]也不可变,即不能修改原型的指向。
;__proto__修改将抛出错误。
var object = {}
Object.preventExtensions(object)
object.__proto__ = {} // Uncaught TypeError: #<Object> is not extensible at set __proto__ [as __proto__] (<anonymous>)
;setPrototypeOf也会抛出错误。
var object = {}
Object.preventExtensions(object)
Object.setPrototypeOf(object, {}) // Uncaught TypeError: #<Object> is not extensible at Function.setPrototypeOf (<anonymous>)
所以preventExtensions作用的对象,不能添加新的属性,也不能修改原型的指向。
Object.isExtensible (opens new window) 可用于判断对象是否可扩展
# Object.seal
;Object.seal (opens new window) 表示封闭对象。
;seal封闭对象时,也会将对象变为不可拓展。所以封闭的对象也有类似preventExtensions的性质,例如不能添加属性,不可修改原型的指向。
var object = { foo: 1 }
Object.seal(object)
Object.isExtensible(object) // false
;seal还会将对象的所有属性的描述符中的configurable置为false。
所以seal作用的对象上的所有属性,都不能删除,也不能修改描述符中除了writable和value之外的键,且writable只能修改为false。
var object = {
foo: 1,
get bar() {},
}
Object.defineProperty(object, 'baz', {
configurable: true,
value: 2,
})
Object.seal(object)
Object.getOwnPropertyDescriptors(object)
// {
// bar: {
// configurable: false,
// enumerable: true,
// get: f bar(),
// set: undefined,
// },
// baz: {
// configurable: false,
// enumerable: false,
// value: 2,
// writable: false,
// },
// foo: {
// configurable: false,
// enumerable: true,
// value: 1,
// writable: true,
// },
// }
Object.isSealed (opens new window) 用于判断对象是否封闭
# Object.freeze
;Object.freeze (opens new window) 表示冻结对象。
;freeze冻结对象时,会将对象封闭并变为不可拓展。
var object = { foo: 1 }
Object.freeze(object)
Object.isSealed(object) // true
Object.isExtensible(object) // false
非严格模式修改属性静默失败,严格模式将抛出错误。
'use strict'
var object = { foo: 1 }
Object.freeze(object)
object.foo = 2 // ncaught TypeError: Cannot assign to read only property 'foo' of object '#<Object>'
object // {foo: 1}
;freeze还会将对象的所有属性的数据描述符中的writable置为false。
var object = { foo: 1 }
Object.defineProperty(object, 'bar', {
configurable: true,
writable: true,
value: 2
})
Object.freeze(object)
Object.getOwnPropertyDescriptors(object)
// {
// bar: {
// configurable: false,
// enumerable: false,
// value: 2,
// writable: false,
// },
// foo: {
// configurable: false,
// enumerable: true,
// value: 1,
// writable: false,
// },
// }
所以freeze冻结的对象几乎不能做任何操作,但是注意freeze只是浅冻结。
var object = {
foo: 1,
bar: {
baz: 2,
},
}
Object.freeze(object)
object.bar.baz = 3
object
// {
// bar: { baz: 3 },
// foo: 1,
// }
Object.isFrozen (opens new window) 用于判断对象是否冻结
# 深度冻结
兼容IE9。
Object.deepFreeze = function (object) {
var prop, value, propNames = Object.getOwnPropertyNames(object)
for (var i = 0; i < propNames.length; i++) {
prop = propNames[i]
value = object[prop]
if (typeof value === 'object' && value !== null) {
Object.deepFreeze(value)
}
}
return Object.freeze(object)
}
注意
Object.keys只能获取到对象自身的不可枚举属性,而getOwnPropertyNames可以获取对象自身的所有非Symbol属性
# 解冻
冻结实际上是不可逆的,无法解冻原对象。
但是可以通过深度克隆并覆盖原对象,达到解冻的目的。
var object = {
foo: 1,
bar: {},
}
Object.deepFreeze(object)
// or JSON.parse(JSON.stringify(object))
object = deepClone(object)
object.bar.baz = 2
object
// {
// foo: 1,
// bar: { baz: 2 },
// }
本质上仅仅是将变量的内存空间指向了另一个对象,原对象还是冻结的。另外若变量object以const的方式声明,此方式也将失效。
注意 JSON.stringify 深拷贝时,可能会丢失掉部分属性
# 数组
数组也是一类特殊的对象,数组下标相当于是对象的键。
var array = [1]
Object.defineProperty(array, 1, {
get() {},
})
Object.getOwnPropertyDescriptors(array)
// {
// 0: {
// configurable: true,
// enumerable: true,
// value: 1,
// writable: true,
// },
// 1: {
// configurable: false,
// enumerable: false,
// get: f get(),
// set: undefined,
// },
// length: {
// configurable: false,
// enumerable: false,
// value: 2,
// writable: true,
// },
// }
# preventExtensions
;preventExtensions作用的数组不能添加元素,也不能修改原型的指向。
var array = [1, 2]
Object.preventExtensions(array)
array.push(3) // Uncaught TypeError: Cannot add property 2, object is not extensible at Array.push (<anonymous>)
# seal
;seal与对象类似,除了有preventExtensions特性外,也不能删除元素等。
var array = [1]
Object.seal(array)
array.pop() // Uncaught TypeError: Cannot delete property '0' of [object Array] at Array.pop (<anonymous>)
# freeze
;freeze也于对象类似,除了有preventExtensions和seal特性外,也不能修改元素。
非严格模式修改元素静默失败,严格模式将抛出错误。
'use strict'
var array = [1]
Object.freeze(array)
array[0] = 2 // index.html:23 Uncaught TypeError: Cannot assign to read only property '0' of object '[object Array]'
array // [1]
# 小结
preventExtensions会将对象变为不可拓展,即不能添加属性,也不能修改原型的指向seal封闭对象,不仅会将对象变为不可拓展,还会将所有属性的描述符中的configurable置为false。所以除了有preventExtensions的特性外,也不能删除属性,不能修改除了writable和value之外的键,writable只能修改为falsefreeze冻结对象,不仅会将对象变为不可拓展和封闭,还会将所有属性的数据描述符的writable置为false。所以除了有preventExtensions和seal特性外,不能修改属性和任何描述符的键值- 数组是特殊的对象,
preventExtensions、seal和freeze作用数组时,与作用对象的特性高度相似
preventExtensions、seal和freeze均是不可逆的,并浅作用于对象或数组
特性表。
| 特性 | preventExtensions | seal | freeze |
|---|---|---|---|
isExtensible | false | false | false |
isSealed | false | true | true |
isFrozen | false | false | true |
| 添加属性 | 否 | 否 | 否 |
| 修改原型的指向 | 否 | 否 | 否 |
configurable置为false | 否 | 是 | 是 |
| 是否可删除属性 | 是 | 否 | 否 |
| 修改描述符的键值 | 可修改 | 不能修改除了writable和value之外的键,writable只能修改为false | 都不能修改 |
writable置为false | 否 | 否 | 是 |
关系图。

# 🎉 写在最后
🍻伙伴们,如果你已经看到了这里,觉得这篇文章有帮助到你的话不妨点赞👍或 Star (opens new window) ✨支持一下哦!
手动码字,如有错误,欢迎在评论区指正💬~
你的支持就是我更新的最大动力💪~
GitHub (opens new window) / Gitee (opens new window)、GitHub Pages (opens new window)、掘金 (opens new window)、CSDN (opens new window) 同步更新,欢迎关注😉~