9

It seems to me that immutable types are impossible in Javascript, or does anyone know of any tricks to create them? Is it a good or bad practice?

For instance something like,

var Point2D = function Point2D(x, y) {

    var _x = x;
    var _y = y;

    function constructor() {

        var self = {};

        // Pseudo-Immutable concept
        self.x = function() {
            return _x;
        }();

        self.y = function() {
            return _y;
        }();

        return self;
    }

    return constructor();

}

Which of course isn't really immutable, but if it were either 1) well-documented that the properties 'x' and 'y' are getter-functions or 2) threw some kind of alert when validating for immutability then it could act as a de-facto immutable object.

Thoughts?

0

6 Answers 6

20

You can use Object.freeze(o); to make an object immutable in newish browsers.

The Point2D could thus be implemented like this:

var Point2D = function(x, y) { 
    this.x = x; 
    this.y = y;
    Object.freeze(this);
}  

Now no new properties can be added to a Point2D object and it's existing properties can not be altered:

> var p = new Point2D(99, 123)
undefined
> p.x
99
> p.x = 7
7
> p.x
99
> p.foo = "This won't be added"
undefined
> JSON.stringify(p);
"{"x":99,"y":123}"

If you only want to lock down the object to not have any new properties added then you can use Object.seal(o); instead. This will allow you to mutate existing properties, but not add new ones.

> var o = {x:1, y:2}
undefined
> Object.seal(o);
[object Object]
> JSON.stringify(o);
"{"x":1,"y":2}"
> o.foo = "This won't be added";
99
> o.x = 37 // Seal allows to mutate object 
37
JSON.stringify(o);
"{"x":37,"y":2}"

freeze and seal is part of ECMAScript 5.1 described more formally here

MDN states that freeze is supported in:

  • Firefox (Gecko) 4 (2.0)
  • Chrome 6
  • Internet Explorer 9
  • Opera 12
  • Safari 5.1

Alternatively, you could use a more functional coding style:

var Point2D = function(x, y) { 
   return function(prop) { 
      switch (prop) {
         case "x": return x;
         case "y": return y;
         default: throw new Error("Property '" + prop + "' not supported");
      }
   };
}

Usage would then be like:

> var p = Point2D(1,2)
undefined
> p("x")
1
> p("y")
2
> p("foo")
Error: Property 'foo' not supported

I know of no way to alter the "properties" x and y using this aproach since they are bound by the scope of the Point2D function. This approach is not commonly seen in javascript (as far as I know), but is similar to how message passing/OO can be achieved in for instance Scheme.

Sign up to request clarification or add additional context in comments.

1 Comment

As @Fresheyeball and @Sulthan pointed, the sub arrays and objects are not immutable by default just using Object.freeze(this), so as I'm doing quick tests here it'd be necessary to freeze all substructures, but I think a good practice is to define constructors for every structure. So if a change is to be made in the Point2D to know in what unit this value is being described the new structure would be {x: {value:99, unit:'cm'}}, but the the x in Point2D would receive a valueWithUnit(99, 'cm') that would be responsible for turning it immutable.
6

If you don't have to worry about older browsers you can look into Object.defineProperty.

Other than that, I don't think there is much of an option since any function/property on an object can be redefined at any point in JavaScript.

2 Comments

It's worth mentioning that writeable is false by default, so creating a immutable property is as easy as writing: Object.defineProperty(obj, 'x', {value: 5});.
@SeanThoman This is not really correct. As of ECMAScript 5.1 there is the freeze function on Object which renders an object immutable. See my answer below for some different ways of achieving immutability in JS.
2

It is possible in javascript to define property getters and setters:

function Point2D(){
    this.__defineGetter__("x", function(){

    });

    this.__defineSetter__("x", function(val){

    });
}

The underlying variables they modify will, however, be mutable.

1 Comment

This is not in the standard, though. Better use Object.defineProperty.
2
Point2D = function(x, y)
{
    var privateX = x;
    var privateY = y;

    return {
        getX: function()
        {
            return privateX;
        },
        getY: function()
        {
            return privateY;
        }
    };
};

var foo = new Point2D(20, 30);
console.log(foo.getX(), foo.getY());

Since privateX and privateY only exist in the scope of the constructor, they can only be accessed by the functions defined in the constructor (getX, getY).

4 Comments

getX and getY still aren't truly immutable, you could re-assign them to basically anything
Right, but the data is immutable. Modifying the getters just makes it inaccessible as well. :-)
The data isn't really immutable either, though it is closer. This is more like a private variable. Especially considering that privateX and privateY can still be changed internally.
With this code, it's immutable for all practical purposes. Yes, code could be added privately to the class to change the data, but at that point it's no longer the same thing. The main issue with this approach is that it's relatively expensive if you're creating a lot of instances.
0

There is now a new javascript reserved word const. const makes runtime constants that are immutable.

Note: const does NOT render objects or arrays to be immutable.

You can be kinda sorta immutable-ish by using Object.freeze to prevent mutation of objects.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const http://caniuse.com/#search=const

DEMO

6 Comments

This is no use for properties. Also, it's not in the standard (yet).
The OP asked about immutable types, not specifically immutable properties. And it IS the standard available in all major browsers kangax.github.io/es5-compat-table/es6/#const
Also it does have some use in properties, as _x can be a const (looking at OP's example)
This is definitely worth mentioning.
Nope, this is confusing const and immutability. const array (or any object) is still mutable.
|
-1

Throwing some ES2015 in the mix can help:

function Point2D(x, y) {
  const instanceX = x,
    instanceY = y;

  return {
    get x() { return instanceX; },
    get y() { return instanceY; }
  }
}

1 Comment

while trying to answer others with a code snippet, you may have to show some result to let them confirm. or else, you should use the code block.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.