如何禁止JavaScript對象重寫?

翻譯 |?Fundebug
不要重寫他人的對象
// 示例代碼1window.originalAlert = window.alert;window.alert = function(msg) {if (typeof msg === "string") {return console.log(msg);}return window.originalAlert(msg);};alert('ooh so awesome'); // 參數(shù)為字符串時,打印到控制臺alert(3.14); // 參數(shù)為其他類型時,彈出對話框
在示例代碼1中,我修改了windows.alert:參數(shù)為字符串時,打印到控制臺;參數(shù)為其他類型時,彈出對話框。這樣的修改顯然會影響其他使用alert方法的開發(fā)者。
如果你修改的是DOM對象比如getElementById(),這會導致非常嚴重的后果。
如果你只是為對象添加新的方法,這也會導致問題。
// 示例代碼2Math.cube = function(n) {return Math.pow(n, 3);};console.log(Math.cube(2)); // 8
這樣做最大的問題是有可能在未來導致命名沖突。盡管Math對象目前并沒有cube方法,下一個版本的JavaScript標準也許會增加cube方法(當然可能性不大),這就意味著我們會把原生cube方法給替代了。
有一個真實的案例,Prototype庫定義了document.getElementsByClassName()方法,而這個方法后來被加入了JavaScript標準。
不幸的是,我們無法阻止其他開發(fā)者重寫我們定義的對象,這時我們就需要本文介紹的這些方法了:
首先,我們不妨通過一個表格對比一下Object.preventExtensions()、Object.seal()和Object.freeze():
| 方法 | 禁止增加屬性 | 禁止刪除屬性 | 禁止修改屬性 |
|---|---|---|---|
| Object.preventExtensions() | 是 | 否 | 否 |
| Object.seal() | 是 | 是 | 否 |
| Object.freeze() | 是 | 是 | 是 |
Object.preventExtensions()
使用Object.preventExtensions(),可以禁止給對象添加新的方法或者屬性。注意,修改或者刪除對象已經(jīng)存在的方法或者屬性是沒有問題的。使用Object.isExtensible()可以查看某個對象是否可以增加方法或者屬性。
// 示例代碼3var song = {title: 'Hope Leaves',artist: 'Opeth'};console.log(Object.isExtensible(song)); //trueObject.preventExtensions(song);console.log(Object.isExtensible(song)); //falsesong.album = 'Damnation';console.log(song.album); // undefinedsong.play = function() {console.log('ahh soo awesome');};song.play(); // TypeError: song.play is not a function
由示例代碼3可知,執(zhí)行Object.preventExtensions()之后,為song對象新增album以及play方法都失敗了!
但是,當我們?yōu)閟ong新增屬性或者方法時,并沒有報錯。當我們使用了”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
在嚴格模式下,給已經(jīng)Object.preventExtensions的對象新增屬性時,會立即報錯。
Object.seal()
使用Object.seal(),可以禁止給對象添加屬性或者方法(這一點與Object.preventExtension()的作用一致),同時禁止刪除對象已經(jīng)存在的屬性或者方法。
// 示例代碼5"use strict"var song = {title: 'Hope Leaves',artist: 'Opeth'};Object.seal(song);console.log(Object.isExtensible(song)); //falseconsole.log(Object.isSealed(song)); //truesong.album = 'Damnation'; // Uncaught TypeError: Cannot add property album, object is not extensibledelete song.artist; // Uncaught TypeError: Cannot delete property 'artist' of #
Object.freeze()
使用Object.freeze(),可以禁止為對象增加屬性或者方法(這一點與Object.preventExtension()的作用一致),同時禁止刪除對象已經(jīng)存在的屬性或者方法(這一點與Object.seal()的作用一致),另外還禁止修改已經(jīng)存在的屬性或者方法。
// 示例代碼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)); // falseconsole.log(Object.isSealed(song)); // trueconsole.log(Object.isFrozen(song)); // truesong.album = 'Damnation'; // Uncaught TypeError: Cannot add property album, object is not extensibledelete song.artist; // Uncaught TypeError: Cannot delete property 'artist' of #song.getLongTitle = function() // Uncaught TypeError: Cannot assign to read only property 'getLongTitle' of object '#{return "foobar";};
主流瀏覽器的最新版本都支持這些方法:
IE 9+
Firefox 4+
Safari 5.1+
Chrome 7+
Opera 12+

