Preventing modification of JavaScript objects

Due to its dynamic nature, JavaScript makes it extremely easy to modify objects that you do not own. It also means that anyone can easily modify objects that you have written. This is seemingly a very powerful feature and many developers may be tempted to use it in order to extend or modify behavior of objects (even DOM methods such as document.getElementById() can be overwritten). Such practice should generally be avoided as it can lead to maintenance problems and can produce hard to find bugs. ECMAScript 5 introduced a bunch of methods that allow programmers to restrict modification of objects. This new language feature can be very helpful when writing libraries or when writing code in bigger teams.

If you don't own it, don't modify it

A good JavaScript rule says that you shouldn't modify objects you don't own. For example if you decide to override a method chances are that you are breaking libraries that depend on it and you are generating a lot of confusion among other developers.

window.originalAlert = window.alert;  
window.alert = function(msg) {  
    if (typeof msg === "string") {
        return console.log(msg);
    }
    return window.originalAlert(msg);
};

alert('ooh so awesome'); // console  
alert(3.14); //alert

Here we modify windows.alert to log all string values to console instead displaying a message box. For other types we invoke the original function. Regardless of our motivation, the result will be a lot of confusion among developers using alert function. Of course playing with DOM objects and methods like getElementById() can lead to far severe consequences (nasty, nasty bugs).

Modifying objects by adding new methods can also be harmful.

Math.cube = function(n) {  
    return Math.pow(n, 3);
};
console.log(Math.cube(2)); // 8

The biggest problem with this approach are naming collisions that may happen in the future. Even if Math object does not contain cube method now, next iteration of JavaScript standard may introduce it (even though this is unlikely). It will mean that we replace native (that is possible faster, better or simply behaving differently) implementation without even knowing this. This may painful and costly maintenance problems in your applications. A real world example of this problem is document.getElementsByClassName() introduced by Prototype library, just to be later included as a part of the standard.

Unfortunately there is no guarantee that other developers will leave your objects alone. If you provide something that in your opinion should be closed to modifications, you can use new JavaScript feature described below.

 Object.preventExtensions()

You can use Object.preventExtensions() to prevent new methods or properties being added to the object. Please note that existing members can be modified or even deleted. To determine whether an object is extensible or not use Object.isExtensible() method.

var song = {  
    title: 'Hope Leaves',
    artist: 'Opeth'
};

console.log(Object.isExtensible(song)); //true  
Object.preventExtensions(song);  
console.log(Object.isExtensible(song)); //false  
//(...)

song.play = function() {  
    console.log('ahh soo awesome');
}; //silently fails
song.album = 'Damnation'; //silently fails

console.log(song.title);  // Hope Leaves  
console.log(song.artist); //Opeth  
delete song.artist;       // we can delete and modify  
console.log(song.artist); // undefined  
console.log(song.album);  // undefined  
song.play(); //error no play() method defined

In this example if you try to add new members to a locked down object the operation will silently fail. This behavior will be changed if we use strict mode.

"use strict";

var song = {  
    title: 'Hope Leaves',
    artist: 'Opeth'
};
Object.preventExtensions(song);  
// (...)
song.album = 'Damnation'; //TypeError

In strict mode an error will be thrown when we try to add new member.

Object.seal()

Use Object.seal() to seal an object. Every sealed object is non-extensible (so it acts as if we acted with Object.preventExtension() on it ), but additionally none of its existing properties or methods can be removed.

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'; //silently fails in non-strict mode  
delete song.artist;       //silently fails in non-strict mode  
console.log(song.artist); // 'Opeth'  
console.log(song.album);  // undefined

Again, in strict mode silent failures will be replaced by errors.

Object.freeze()

Frozen objects are considered to be sealed (and non-extensible as well). The additional constraint is that no modifications to existing properties or methods can occur.

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'; //silently fails in non-strict mode  
delete song.artist; //silently fails in non-strict mode  
song.getLongTitle = function() {  
    return "foobar";
}; //silently fails in non-strict mode
console.log(song.getLongTitle()); // Opeth - Hope Leaves  
console.log(song.artist); // 'Opeth'  
console.log(song.album); // undefined

In strict mode instead of silent failures we would see errors.

This methods should be supported by any recent version of all major browsers:

  •  IE 9+ (this means it should work in WinJS, but haven't tested it),
  •  Firefox 4+
  •  Safari 5.1+
  •  Chrome 7+
  •  Opera 12+

 

comments powered by Disqus