很多編程語言提供了const關(guān)鍵詞聲明一個(gè)常量,在ES6中也是提供了const,但是在前端的const與其他編程語言不同,其并不意味著聲明的變量就是一個(gè)常量。使用const b = {}聲明了一個(gè)常量b,但是通過使用b.a = 1去修改對(duì)象b卻并沒有報(bào)錯(cuò),我們修改了一個(gè)原本以為是常量實(shí)際上是變量的對(duì)象。
為什么會(huì)這樣?
實(shí)際上,const定義的變量保存的是指向?qū)嶋H數(shù)據(jù)的指針,對(duì)于基本數(shù)據(jù)類型String、Boolean、Number、undefined、null、Symbol而言,
其值保存在棧內(nèi)存中的簡(jiǎn)單數(shù)據(jù)段,按值訪問,就是等同于常量。但是相對(duì)于引用數(shù)據(jù)類型而言,const只能保證指向保存在堆內(nèi)存中的對(duì)象的指針保持不變,換句話說
const能夠保證變量始終指向同一個(gè)對(duì)象,至于對(duì)象的修改無能為力。
所以,在前端中到底如何實(shí)現(xiàn)一個(gè)常量!
Object.freeze
Object.freeze可以凍結(jié)對(duì)象,不能新增和刪除屬性,同時(shí)對(duì)象已有屬性都是不可枚舉、不可配置、不可寫。需要注意的是使用該方法只能讓對(duì)象淺凍結(jié),其內(nèi)部屬性為對(duì)象時(shí)
依然能夠被篡改,要想實(shí)現(xiàn)完全凍結(jié),那么就需要進(jìn)行如下操作。
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
該方法可以將對(duì)象變?yōu)椴豢蓴U(kuò)展即對(duì)象即不能添加新的屬性,但是對(duì)象的原有屬性依然可以被刪除或修改,同時(shí)如果屬性的值為對(duì)象,盡管設(shè)置了
不能被添加屬性,但是其屬性值為對(duì)象的屬性依舊可以添加屬性。
舉個(gè)例子:
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相比,該方法同樣能夠?qū)?duì)象變?yōu)椴荒芴砑有聦傩裕⑶以摲椒ń箘h除對(duì)象的屬性。同樣如果屬性的值為對(duì)象,
屬性值依舊可以添加新屬性或刪除屬性。
舉個(gè)例子
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中大放異彩,使用其也能夠?qū)?duì)象完整凍結(jié)。在寫代碼之前我們
先了解下writable、Configurable需要知道都內(nèi)容,這才是此次凍結(jié)的關(guān)鍵。
writable
對(duì)象屬性的值是否能夠被重寫,為true表示允許,為false即被禁止,默認(rèn)為false。如果屬性的值為對(duì)象,
盡管設(shè)置了不能被重寫,其屬性為對(duì)象的值依舊能夠被重寫。
舉個(gè)例子:
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},如果屬性的值為對(duì)象,盡管設(shè)置了不能被重寫,其屬性為對(duì)象的值依舊能夠被重寫。
console.log(obj.c);
Configurable
configurable特性表示對(duì)象的屬性是否可以被刪除,以及除writable特性外的其他特性是否可以被修改。為true表示允許被修改
false表示禁止修改,默認(rèn)為false,如果屬性的值為對(duì)象,盡管設(shè)置了屬性不能被修改,其屬性為對(duì)象的屬性依舊能夠被修改。
舉個(gè)例子
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:{}},如果屬性的值為對(duì)象,盡管設(shè)置了屬性不能被修改,其屬性為對(duì)象的屬性依舊能夠被修改。
console.log(obj);
上面這三個(gè)方法單獨(dú)拿出來并不能夠完美的將對(duì)象變?yōu)橐粋€(gè)常量,但是我們組合一下就可以生成一個(gè)常量。
function deepConst(data){
if (!data || typeof data !== 'object') {
return;
}
//Object.preventExtensions(data);也可以實(shí)現(xiàn)
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在目標(biāo)對(duì)象之前進(jìn)行了一層攔截,外界對(duì)對(duì)象的訪問和修改都需要通過這層攔截,所以我們可以操控?cái)r截來控制對(duì)對(duì)象對(duì)訪問和修改。Proxy
支持的攔截操作眾多,下面只列舉與文章相關(guān)的操作,如果想更深入了解Proxy,請(qǐng)看這篇文章。
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, []);
}