const声明的常量是不允许被修改的,但是如果保存的是引用值,const只能保证栈指向堆的指针不变,不能保证引用值的内容不变
那么如何保证对象不可修改呢?
Object.defineProperty()
ES5 提供了 Object.defineProperty 方法,该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性。
Object.defineProperty(obj, prop, descriptor)
参数:
- obj 要定义属性的对象
- prop 一个字符串或 Symbol,指定了要定义或修改的属性键
- descriptor 要定义或修改的属性的描述符
- 数据描述符
- configurable: 是否可删除 / 可配置descriptor
- enumerable:是否可枚举
- value:该属性的值,默认undefined
- writable:是否可写
- 访问器描述符
- configurable
- enumerable
- get:属性值获取拦截
- set:属性值赋值拦截
- 数据描述符
描述符只能是这两种类型之一,不能同时为两者,比如当描述符既有value,也有get的时候,是冲突的
通过赋值添加的对象属性 可写 / 可删可配置 / 可遍历,通过使用 defineProperty 添加的属性 默认情况下 不可写 / 不可枚举 / 不可配置
结合writable: false和configurable: false就可以创建一个真正的常量属性
const obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, 'a', {
writable: false,
configurable: false
})
obj.a = 'change';
delete obj.b;
obj.c = 'new val';
console.log(obj); // {a: 1, c: 'new val'}
可以确保对象的某个属性不可改,但不能阻止对象 新增 / 删除 属性
Proxy 监听对象操作
对 set / delete 操作进行拦截,不允许设置和删除操作
let targetObj = {
a: 1
}
let proxyObj = new Proxy(targetObj, {
set() {
throw new Error('不允许修改')
},
deleteProperty() {
throw new Error('不允许删除')
}
})
proxyObj.b = 2
proxyObj.a = 2
delete(proxyObj.a)
console.log(proxyObj)
Object.preventExtensions 不可拓展
让一个对象变得不可拓展,也就是无法 新增自身的属性,但是可以修改 / 删除原有属性
并且该方法只能防止添加自有属性,但原型依然可以添加新的属性,但是不能重新赋值[[prototype]]
const obj = {
a: 1,
b: 2
}
Object.preventExtensions(obj)
// // 报错 Cannot define property c, object is not extensible
// obj.c = 'new val';
// 报错
// Object.defineProperty(obj, 'c', {
// val: 'defineProperty newVal',
// writable: false,
// configurable: false
// })
obj.a = 'change'; // 修改成功
delete obj.b; // 删除成功
obj.__proto__.d = 'new proto val'
// 报错 #<Object> is not extensible at set __proto__ [as __proto__]
// obj.__proto__ = { 'oh': 'hello' }
console.log(obj); // {a: 'change', [[prototype]]: { 'oh': 'hello', ... }}
Object.seal() 封闭
不可新增属性,将所有现有属性标记为不可配置,不可删除原有属性,可以修改原有属性
let obj = {
a: '1'
}
Object.seal(obj);
obj.b = 2;
delete obj.a;
console.log(obj); // {a: '1'}
Object.freeze()冻结 ☑️
无法新增 / 修改 / 删除属性,保证冻结的对象中的现有属性值是不可变
但是Object.preventExtensions / Object.seal / Object.freeze都存在两个问题:
- 可以通过改变原型对象,来为对象增加属性
- 目标对象内部的对象不会冻结
let obj = {
a: '1',
like: ['coding']
}
Object.freeze(obj);
obj.a = '2';
obj.c = 'proto val';
obj.like.push('liderder');
console.log(obj); // { a: '1', like: ['coding', 'liderder'], [[Prototype]]: { c: proto val, ... } }
可以手动实现一个冻结函数解决浅层冻结的问题
function deepFreeze(obj) {
if (typeof obj != 'object') {
throw Error('Type Error!')
}
Object.freeze(obj);
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
if (typeof obj[key] === 'object') {
deepFreeze(obj[key])
}
}
}
}
但是仍然可以通过原型增加属性
还有什么其他方案吗?欢迎各位大佬指正