先记住这个答案
configurable: false 之后,属性不能被删除,描述符也基本锁死:enumerable 不能改,configurable 不能再改回 true,数据属性不能换成 get / set 访问器,反过来也不行。只有两件事还能做:writable 为 true 时照常改值,以及把 writable 从 true 改成 false。所以它管的是「这个属性长什么样」,不是「这个属性的值是多少」。
- 不可配置属性不能删除
- 不可配置后不能改枚举性或属性类型
writable可单向关闭- 不影响改值,锁值要靠
writable: false
逐条试一遍
面试里光背规则容易漏,直接拿一个 configurable: false、writable: true 的属性挨个试最清楚。
const obj = {}
Object.defineProperty(obj, 'id', { value: 1, writable: true, enumerable: true, configurable: false })
console.log(Reflect.deleteProperty(obj, 'id')) // false,删不掉
obj.id = 2
console.log(obj.id) // 2,writable 还是 true,照样能改值
const tryDefine = (desc) => {
try {
Object.defineProperty(obj, 'id', desc)
console.log('ok')
} catch (e) {
console.log(e.name)
}
}
tryDefine({ enumerable: false }) // 改 enumerable
tryDefine({ get() { return 3 } }) // 数据属性换成访问器
tryDefine({ writable: false }) // true 改成 false,唯一放行的方向
tryDefine({ writable: true }) // 再改回 true查看输出与解释
false
2
TypeError
TypeError
ok
TypeError这里用 Reflect.deleteProperty 是为了拿到布尔返回值。换成 delete obj.id,非严格模式下同样返回 false,严格模式下直接抛 TypeError。
Object.defineProperty 不管在不在严格模式,改不了都会抛 TypeError。这点和普通赋值不一样,普通赋值给只读属性在非严格模式下是静默失败。
最后两行说明 writable 是单向的:可以从 true 降到 false,降完之后这个属性就彻底成了常量,值和描述符都动不了了。
为什么允许 writable 往下改
规范的思路是:configurable: false 承诺的是「属性不会变得更宽松」。把 writable 从 true 改成 false 是在收紧权限,不会破坏任何人对这个属性的假设,所以放行。反过来从 false 改成 true,本来的常量突然能改了,就违反了承诺。
还有个细节:值没变的「重新定义」不会报错。比如上面的 id 已经是常量,值是 2,再调用 Object.defineProperty(obj, 'id', { value: 2 }) 不会报错。新值和旧值用 Object.is 比较相同,就当作没改;传 3 才会抛 TypeError。
- 和
Object.seal、Object.freeze的关系 Object.seal(obj)做两件事:禁止新增属性,把所有自有属性都设成configurable: false。所以被 seal 的对象,属性能改值、不能删除、不能重新定义。Object.freeze(obj)在 seal 基础上,再把所有数据属性的writable设成false,就成了只读。理解了configurable和writable这两个开关,seal 和 freeze 的区别就不用死记。
容易答错的地方
- 把
configurable: false当成只读 - 它不锁值。值能不能改只看
writable,访问器属性则看有没有set。想让属性只读,要设writable: false。 - 以为
defineProperty默认就是可配置的 - 用
Object.defineProperty新增属性时,没写的writable、enumerable、configurable全部默认false。所以随手写一句Object.defineProperty(obj, 'x', { value: 1 }),这个属性就已经删不掉了。而普通赋值obj.x = 1创建的属性,三个开关都是true。 - 以为访问器属性锁住后还能换 getter
- 访问器属性
configurable: false后,get和set函数都不能换成别的函数,只有传入和原来同一个函数引用才不报错。
面试官还会怎么问?
内置对象上有哪些 configurable: false 的属性?
比如 Math.PI,它是 writable: false、configurable: false,所以 Math.PI = 3 无效,delete Math.PI 返回 false。还有用 var 在全局声明的变量,挂在全局对象上的那个属性也是 configurable: false,所以 delete 删不掉。
Vue 2 的响应式为什么对某些属性不生效?
Vue 2 用 Object.defineProperty 把属性改成 getter 和 setter。源码里会先检查属性描述符,遇到 configurable: false 的属性就直接跳过,因为没法重新定义它。被 Object.freeze 过的数据也是这个原因不会变成响应式。
怎么查看一个属性当前的描述符?
用 Object.getOwnPropertyDescriptor(obj, 'id') 查单个,Object.getOwnPropertyDescriptors(obj) 查全部。返回的对象里能直接看到 value、writable、enumerable、configurable 四个字段,访问器属性则是 get、set、enumerable、configurable。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。