前端如何定義一個常量
很多程式語言提供了const
關鍵詞宣告一個常量,在ES6中也是提供了const
,但是在前端的const
與其他程式語言不同,其並不意味著宣告的變數就是一個常量。使用const b = {}
聲明瞭一個常量b,但是通過使用b.a = 1
去修改物件b卻並沒有報錯,我們修改了一個原本以為是常量實際上是變數的物件。
為什麼會這樣?
實際上,const
定義的變數儲存的是指向實際資料的指標,對於基本資料型別String、Boolean、Number、undefined、null、Symbol
而言,
其值儲存在棧記憶體中的簡單資料段,按值訪問,就是等同於常量。但是相對於引用資料型別而言,const
只能保證指向儲存在堆記憶體中的物件的指標保持不變,換句話說const
能夠保證變數始終指向同一個物件,至於物件的修改無能為力。
所以,在前端中到底如何實現一個常量!
Object.freeze
Object.freeze
可以凍結物件,不能新增和刪除屬性,同時物件已有屬性都是不可列舉、不可配置、不可寫。需要注意的是使用該方法只能讓物件淺凍結,其內部屬性為物件時
依然能夠被篡改,要想實現完全凍結,那麼就需要進行如下操作。
function deepConst(data){ Object.freeze(data); for(let key in data){ let prop = data[key]; if(!data.hasOwnProperty(key) || !(typeof prop === "object") || Object.isFrozen(prop)){ continue; } deepConst(prop); } } 複製程式碼
Object.defineProperty、Object.preventExtensions、Object.seal
Object.preventExtensions
該方法可以將物件變為不可擴充套件即物件即不能新增新的屬性,但是物件的原有屬性依然可以被刪除或修改,同時如果屬性的值為物件,儘管設定了 不能被新增屬性,但是其屬性值為物件的屬性依舊可以新增屬性。
舉個例子:
let obj = {a:1,b:2,c:{d:3}}; Object.preventExtensions(obj); obj.d = 1; obj.a = 2; delete obj.b; obj.c.e = 10; //輸出{a:1,c:{d:3,e:10} console.log(obj); 複製程式碼
Object.seal
與Object.preventExtensions
相比,該方法同樣能夠將物件變為不能新增新屬性,並且該方法禁止刪除物件的屬性。同樣如果屬性的值為物件,
屬性值依舊可以新增新屬性或刪除屬性。
舉個例子
let obj = {a:1,b:2,c:{d:3}}; Object.seal(obj); obj.e = 10; delete obj.a; delete obj.c.d; obj.c.f = 10; //輸出{a:1,b:2,c:{f:10} console.log(obj); 複製程式碼
Object.defineProperty
Object.defineProperty(obj, prop, descriptor)
在MVVM中大放異彩,使用其也能夠將將物件完整凍結。在寫程式碼之前我們
先了解下writable、Configurable
需要知道都內容,這才是此次凍結的關鍵。
writable
物件屬性的值是否能夠被重寫,為true表示允許,為false即被禁止,預設為false。如果屬性的值為物件, 儘管設定了不能被重寫,其屬性為物件的值依舊能夠被重寫。
舉個例子:
let obj = {a:1,b:2,c:{d:3}}; Object.defineProperty(obj,"a",{writable:true}); Object.defineProperty(obj,"b",{writable:false}); Object.defineProperty(obj,"c",{writable:false}); Object.defineProperty(obj,"e",{writable:false}); obj.a = 2; obj.b = 3; obj.c.d = 4; //輸出為2,即a屬性的值被重寫了 console.log(obj.a); //輸出依然為2,即b屬性的值沒有被重寫 console.log(obj.b); //輸出依然為{d:4},如果屬性的值為物件,儘管設定了不能被重寫,其屬性為物件的值依舊能夠被重寫。 console.log(obj.c); 複製程式碼
Configurable
configurable
特性表示物件的屬性是否可以被刪除,以及除writable
特性外的其他特性是否可以被修改。為true表示允許被修改
false表示禁止修改,預設為false,如果屬性的值為物件,儘管設定了屬性不能被修改,其屬性為物件的屬性依舊能夠被修改。
舉個例子
let obj = {a:1,b:2,c:{d:3}}; Object.defineProperty(obj,"a",{configurable:true}); Object.defineProperty(obj,"b",{configurable:false}); Object.defineProperty(obj,"c",{configurable:false}); delete obj.a; delete obj.b; delete obj.c; //輸出 {b:2,c:{}},如果屬性的值為物件,儘管設定了屬性不能被修改,其屬性為物件的屬性依舊能夠被修改。 console.log(obj); 複製程式碼
上面這三個方法單獨拿出來並不能夠完美的將物件變為一個常量,但是我們組合一下就可以生成一個常量。
function deepConst(data){ if (!data || typeof data !== 'object') { return; } //Object.preventExtensions(data);也可以實現 Object.seal(data); Object.keys(data).forEach(function(key) { unWriteConfig(data, key, data[key]); }); } function unWriteConfig(data, key, val) { deepConst(val); Object.defineProperty(data, key, { writable:false, configurable:false }); } 複製程式碼
Proxy
Proxy
在目標物件之前進行了一層攔截,外界對物件的訪問和修改都需要通過這層攔截,所以我們可以操控攔截來控制對物件對訪問和修改。Proxy
支援的攔截操作眾多,下面只列舉與文章相關的操作,如果想更深入瞭解Proxy
,請看這篇文章
。
function createDeepProxy(target) { function makeHandler() { return { set(target, key, value, receiver) { return false; }, deleteProperty(target, key) { return false; } } } function proxify(obj, path) { for(let key of Object.keys(obj)) { if(typeof obj[key] === 'object') { obj[key] = proxify(obj[key], [...path, key]); } } let p = new Proxy(obj, makeHandler()); return p; } return proxify(target, []); } 複製程式碼