OO – Immutability freeze

by Joshua McNeese

JavaScript

var obj = {
    prop: function () {},
    foo: "bar"
};

// New properties may be added, existing properties may be changed or removed
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;

var o = Object.freeze(obj);

console.log(Object.isFrozen(obj) === true);

// Now any changes will fail
obj.foo = "quux"; // silently does nothing
obj.quaxxor = "the friendly duck"; // silently doesn't add the property

// ...and in strict mode such attempts will throw TypeErrors
function fail() {

    "use strict";

    try {

        obj.foo = "sparky"; // throws a TypeError
        delete obj.quaxxor; // throws a TypeError
        obj.sparky = "arf"; // throws a TypeError
        // Attempted changes through Object.defineProperty will also throw
        Object.defineProperty(obj, "ohai", {
            value: 17
        }); // throws a TypeError
        Object.defineProperty(obj, "foo", {
            value: "eit"
        }); // throws a TypeError   

    } catch (e) {

        console.log(e);

    }

}

fail();

obj = {
    internal: {}
};

Object.freeze(obj);
obj.internal.a = "aValue";

console.log(obj.internal.a) // "aValue"

// To make obj fully immutable, freeze each object in obj.
// To do so, we use this function.

function deepFreeze(o) {

    var prop, propKey;

    Object.freeze(o); // First freeze the object.

    for (propKey in o) {

        prop = o[propKey];

        if (!o.hasOwnProperty(propKey) || !(typeof prop === "object") || Object.isFrozen(prop)) {

            // If the object is on the prototype, not an object, or is already frozen, 
            // skip it. Note that this might leave an unfrozen reference somewhere in the
            // object if there is an already frozen object containing an unfrozen object.
            continue;

        }

        deepFreeze(prop); // Recursively call deepFreeze.

    }

}

var obj2 = {
    internal: {}
};

deepFreeze(obj2);
obj2.internal.a = "anotherValue";
console.log(obj2.internal.a); // undefined