【每日一題】如何設置對象的屬性不可添加或刪除

人生苦短,總需要一點儀式感。比如學前端~
如何設置對象不可添加或者刪除屬性
一共有如下幾種方式:
Object.defineProperty() Object.seal() Object.freeze() Proxy
前置知識:對象屬性的數據描述符
對象的屬性有幾個數據描述符:
configurable表示是否可配置,作用有2:屬性數據描述符是否可被改變 (除value和writable特性外的其他描述符) 屬性是否可被刪除 enumerable: 表示屬性是否可枚舉value表示屬性的值writable表示屬性的值是否可以修改
默認值
使用 Object.defineProperty() 添加的屬性值是不可修改(immutable)的。
這主要和幾個屬性的默認值有關:
const object1 = {};
Object.defineProperty(object1, 'property1', {
});
// 不填寫屬性數據描述符的前提下,獲取生成的描述符默認值
console.log(Object.getOwnPropertyDescriptor(object1, 'property1'))

查看對象屬性描述符
Object.getOwnPropertyDescriptor() 方法,用來查看指定對象上某個非原型屬性的屬性描述符。
特別記憶:
對象屬性的 configurable 鍵值為 true 時,該屬性的描述符才能夠被改變,同時該屬性也能從對應的對象上被刪除。
當試圖改變不可配置屬性的值時 (除了 value 和 writable 屬性之外) ,會拋出TypeError,除非當前值和新值相同。

實現方法講解如下
defineProperty、defineProperties
根據上邊屬性描述符的作用我們可以推斷,如果將 configurable 的值為 false 的時候,屬性就無法從對象上刪除。
Object.defineProperty(obj, "name", {
configurable: false, // 當且僅當該屬性的 configurable 鍵值為 true 時,該屬性的描述符才能夠被改變,同時該屬性也能從對應的對象上被刪除。
enumerable: false, // 當且僅當該屬性的 enumerable 鍵值為 true 時,該屬性才會出現在對象的枚舉屬性中。
writable: false, // 表示是否可以修改屬性的值。
value: "小石頭", // 該屬性(name)對應的值??梢允侨魏斡行У?JavaScript 值(數值,對象,函數等)。
});
像上邊代碼這樣設置之后,name屬性就變成了不能刪除、不可重新修改特性、不可枚舉、不能修改屬性值的狀態(tài)。
但是只這么設置的話,目前obj對象只實現了元素無法被刪除,我們還是能往對象中添加新的屬性。
obj.address = "beijing";
console.log(obj); // { address: "beijing", name: "小石頭" }
Object.seal()
一個對象是可擴展的(可以添加新的屬性)。而Object.seal()方法會封閉一個對象:
阻止添加新屬性 并將所有現有屬性標記為不可配置(即不可刪除和不可修改屬性的某幾個數據描述符)。 不過,當前屬性的值只要原來是可寫的就可以改變。也就是說屬性的值仍然可以修改。
嘗試刪除一個密封對象的屬性或者將某個密封對象的屬性從數據屬性轉換成訪問器屬性,結果會靜默失敗或拋出 TypeError (在嚴格模式中最常見的,但不唯一)。
const obj = { name: "小石頭" };
Object.getOwnPropertyDescriptors(obj);

封閉對象:
Object.seal(obj);
Object.getOwnPropertyDescriptors(obj);

這時如果再去配置就失效了
Object.defineProperty(obj, "name", {
configurable: true
});
Object.getOwnPropertyDescriptors(obj);

同時也沒法添加新屬性了
obj.age = 18

Object.freeze()
Object.freeze() 方法可以凍結一個對象。凍結后的對象有如下特點:
一個被凍結的對象再也不能被修改; 不能向凍結對象添加新的屬性, 不能刪除凍結對象的已有屬性, 不能修改凍結對象已有屬性的可枚舉性、可配置性、可寫性以及不能修改已有屬性的值。 此外,凍結一個對象后該對象的原型也不能被修改。
??注意:Object.freeze()方法實現的是淺凍結
const obj = {
name: "小石頭",
info: {
address: "beijing",
},
};
const freezeObj = Object.freeze(obj); // freeze()返回和傳入的參數相同的對象
// 不能修改凍結對象已有屬性值
freezeObj.name = "石頭姐";
console.log(freezeObj.name); // 仍然是小石頭
// 不能往凍結對象新加內容
freezeObj.newName = '石頭姐'
console.log(freezeObj.name); // undefined,增加失敗
// 刪除凍結對象的已有屬性也不成功
delete freezeObj.name
console.log(freezeObj.name); // 仍然是小石頭

嚴格模式下,操作凍結對象的屬性會報錯
("use strict");
freezeObj.name = "石頭姐"; // TypeError
//Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Object>'

深層嵌套的對象沒有被凍結:
// 在這里,info 是沒有被凍結的
freezeObj.info.newName = "BJ";
console.log(freezeObj.info.newName); // BJ
ES6 的 Proxy 方法
?Proxy 可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。
?
From:https://es6.ruanyifeng.com/#docs/proxy
它在 API 使用者和對象之間扮演了一個中間人的角色。我們可以使用 Proxy 來控制被訪問的底層對象的屬性的行為。
const target = {
a: "我是不能刪除的",
b: "我是不能修改的",
c: "也不能添加,我只有3個元素",
};
const lockTraget = new Proxy(target, {
// 攔截對象屬性的設置,返回false不讓設置屬性
set(target, property, value) {
console.log("攔截修改、新增操作", target, property, value);
return false;
},
// 攔截delete proxy[propKey]的操作,返回false不讓刪除
deleteProperty(target, property) {
console.log("攔截刪除操作");
return false;
},
// 攔截Object.defineProperty()、Object.defineProperties()操作,
defineProperty(target, property, descriptor) {
console.log("defineProperty()");
return false;
},
});
lockTraget.newName = "石頭姐";
console.log(lockTraget.newName); // undefined,新增失敗
delete lockTraget.a;
console.log(lockTraget.a); // "我是不能刪除的", 刪除失敗
lockTraget.b = '小石頭'
console.log(lockTraget.b); // "我是不能修改的", 修改失敗
最后,我們掙扎的想用Object.defineProperty修改屬性數據描述符,也報錯了

讓我們一起攜手同走前端路!
關注公眾號回復【加群】即可

