譯者按: 使用Object.preventExtensions()、Object.seal()和Object.freeze(),可以禁止重寫JavaScript物件。
由於JavaScript的靈活性,我們可以輕易地重寫(override)一些於其他人定義的物件(object)。換句話說,任何人都可以重寫我們所定義的物件。這是一個非常強大的特性,許多開發者都有興趣試試,來擴充或者修改某些物件的行為。例如,DOM方法document.getElementById()都可以被重寫。一般來講,我們應該避免這樣做,因為這會導致程式碼很難維護,並且會留下一些難於發現的BUG。ECMAScript 5引入了一些方法,允許開發者限制物件重寫。如果你在開發一些工具庫比如jQuery, fundebug等, 或者你的開發團隊非常大,本文介紹的這些方法將非常有用。
不要重寫他人的物件
不要重寫他人的物件,這是JavaScript的黃金法則。比如,當你重寫了一個方法,則很可能這會影響依賴於該方法的庫,這會讓其他開發者非常困惑。
// 示例程式碼1
window.originalAlert = window.alert;
window.alert = function(msg) {
if (typeof msg === "string") {
return console.log(msg);
}
return window.originalAlert(msg);
};
alert(`ooh so awesome`); // 引數為字串時,列印到控制檯
alert(3.14); // 引數為其他型別時,彈出對話方塊
複製程式碼
在示例程式碼1中,我修改了windows.alert:引數為字串時,列印到控制檯;引數為其他型別時,彈出對話方塊。這樣的修改顯然會影響其他使用alert方法的開發者。如果你修改的是DOM物件比如getElementById(),這會導致非常嚴重的後果。
如果你只是為物件新增新的方法,這也會導致問題。
// 示例程式碼2
Math.cube = function(n) {
return Math.pow(n, 3);
};
console.log(Math.cube(2)); // 8
複製程式碼
這樣做最大的問題是有可能在未來導致命名衝突。儘管Math物件目前並沒有cube方法,下一個版本的JavaScript標準也許會增加cube方法(當然可能性不大),這就意味著我們會把原生cube方法給替代了。有一個真實的案例,Prototype庫定義了document.getElementsByClassName()方法,而這個方法後來被加入了JavaScript標準。
不幸的是,我們無法阻止其他開發者重寫我們定義的物件,這時我們就需要本文介紹的這些方法了:
首先,我們不妨通過一個表格對比一下Object.preventExtensions()、Object.seal()和Object.freeze():
方法 | 禁止增加屬性 | 禁止刪除屬性 | 禁止修改屬性 |
---|---|---|---|
Object.preventExtensions() | 是 | 否 | 否 |
Object.seal() | 是 | 是 | 否 |
Object.freeze() | 是 | 是 | 是 |
Object.preventExtensions()
使用Object.preventExtensions(),可以禁止給物件新增新的方法或者屬性。注意,修改或者刪除物件已經存在的方法或者屬性是沒有問題的。使用Object.isExtensible()可以檢視某個物件是否可以增加方法或者屬性。
// 示例程式碼3
var song = {
title: `Hope Leaves`,
artist: `Opeth`
};
console.log(Object.isExtensible(song)); //true
Object.preventExtensions(song);
console.log(Object.isExtensible(song)); //false
song.album = `Damnation`;
console.log(song.album); // undefined
song.play = function() {
console.log(`ahh soo awesome`);
};
song.play(); // TypeError: song.play is not a function
複製程式碼
由示例程式碼3可知,執行Object.preventExtensions()之後,為song物件新增album以及play方法都失敗了!
但是,當我們為song新增屬性或者方法時,並沒有報錯。當我們使用了”use strict”採用嚴格模式時,情況就不一樣了:
// 示例程式碼4
"use strict";
var song = {
title: `Hope Leaves`,
artist: `Opeth`
};
Object.preventExtensions(song);
song.album = `Damnation`; // Uncaught TypeError: Cannot add property album, object is not extensible
複製程式碼
在嚴格模式下,給已經Object.preventExtensions的物件新增屬性時,會立即報錯。廣告:如果你希望實時監控應用中類似的錯誤,歡迎免費試用Fundebug。
Object.seal()
使用Object.seal(),可以禁止給物件新增屬性或者方法(這一點與Object.preventExtension()的作用一致),同時禁止刪除物件已經存在的屬性或者方法。
// 示例程式碼5
"use strict"
var song = {
title: `Hope Leaves`,
artist: `Opeth`
};
Object.seal(song);
console.log(Object.isExtensible(song)); //false
console.log(Object.isSealed(song)); //true
song.album = `Damnation`; // Uncaught TypeError: Cannot add property album, object is not extensible
delete song.artist; // Uncaught TypeError: Cannot delete property `artist` of #<Object>
複製程式碼
Object.freeze()
使用Object.freeze(),可以禁止為物件增加屬性或者方法(這一點與Object.preventExtension()的作用一致),同時禁止刪除物件已經存在的屬性或者方法(這一點與Object.seal()的作用一致),另外還禁止修改已經存在的屬性或者方法。
// 示例程式碼6
"use strict"
var song = {
title: `Hope Leaves`,
artist: `Opeth`,
getLongTitle: function()
{
return this.artist + " - " + this.title;
}
};
Object.freeze(song);
console.log(Object.isExtensible(song)); // false
console.log(Object.isSealed(song)); // true
console.log(Object.isFrozen(song)); // true
song.album = `Damnation`; // Uncaught TypeError: Cannot add property album, object is not extensible
delete song.artist; // Uncaught TypeError: Cannot delete property `artist` of #<Object>
song.getLongTitle = function() // Uncaught TypeError: Cannot assign to read only property `getLongTitle` of object `#<Object>`
{
return "foobar";
};
複製程式碼
主流瀏覽器的最新版本都支援這些方法:
- IE 9+
- Firefox 4+
- Safari 5.1+
- Chrome 7+
- Opera 12+