0

There is probably a question like this, but I just wanted to ask. Maybe it will be helpful to somebody.

I have this code:

window.WML.namespace('Cards', {}, (function (wml) {
 'use strict';

  var layout = {
    '4': [
      [
        {x: X_POS_3, y: Y_POS_1}, {x: X_POS_4, y: Y_POS_1},
        {x: X_POS_5, y: Y_POS_2}, {x: X_POS_6, y: Y_POS_2}
      ],
      [
        {x: X_POS_1, y: Y_POS_1}, {x: X_POS_2, y: Y_POS_1},
        {x: X_POS_4, y: Y_POS_2}, {x: X_POS_5, y: Y_POS_2}
      ],
      [
        {x: X_POS_2, y: Y_POS_1}, {x: X_POS_5, y: Y_POS_1},
        {x: X_POS_4, y: Y_POS_2}, {x: X_POS_5, y: Y_POS_2}
      ]
    ],
    '5': [
      // similar code
    ]
  };

  return {
    cardLayout: function () {
      return layout;
    }
  };
}(window.WML)));

I could easily do this in Firebug:

var myLayout = WML.Cards.cardLayout();
myLayout['4'] = 34;

console.log(WML.Cards.cardLayout()); // prints {'4': 34, '5': []}

Note:

namespace(ObjectName, inheritObject, newObjectProperties);

Creates subobject inside WML called ObjectName, which inherits properties from inheritObject plus properties/methods from newObjectProperties.

How would you make cardLayout() return object with immutable subarrays, if I know that there is Array.prototype.slice() method which creates shallow copy of caller array?

8
  • 1
    Use Object.freeze developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… However, you can still modify arrays though. But myLayout['4'] = 34; will not work anymore. Commented Apr 7, 2015 at 13:54
  • I set Object.create(layout); as a return value of cardLayout(). It does the job. But still if I do myLayout['4'][2] = 34; WML.Cards.cardLayout()['4'][2] will return 34 instead of the right value. I am searching for a more general solution. None of the arrays inside should be editable. Commented Apr 7, 2015 at 13:55
  • 1
    You could use immutable data structures in your program. Facebook's immutable package looks promising. facebook.github.io/immutable-js Commented Apr 7, 2015 at 14:11
  • 2
    Facebook's immutable-js is a good solution if you're doing a lot of this. However, if you only need this one use case I would suggest the npm module deep-freeze npmjs.com/package/deep-freeze Commented Apr 7, 2015 at 14:42
  • 1
    @Vlad: Everybody can hack his instantiation of your game using firebug. There's no way around that - only a few more or less complicated ones to make hacking non-trivial. Make sure your highscore server is appropriately secured. Commented Apr 12, 2015 at 12:36

2 Answers 2

1

You could use setters/getters to prevent accidentally writing to an object that should be immutable:

var o = (function() {
    var internal_o = {x:3};
    var internal_y = 5;
    Object.defineProperty(o, "y", {
        get: function(){ return internal_y },
        set: function(){ /*do nothing*/ },
    }
    return internal_o;
})();
o.y = 3; // Does nothing
console.log(o.y) // 5

We're storing the data inside a closure, so it's impossible to access it directly. You'd have to do this (recursively) for every property you wanted to be immutable, though -- you'd probably want to generalize the code so that you can call a function like defineImmutableProperty(obj, "propertyName", value). In your example, if you set the '4' property to be immutable, you'd still be able to change elements of the array, or the x and y properties of those points.

And if you want to make an array immutable, you could do something like this:

var o = (function() {
    var internal_o = {x:3};
    var internal_array = [1, 2, 3];
    Object.defineProperty(o, "numbers", {
        get: function(){ return internal_array.slice() },
        set: function(){ /*do nothing*/ },
    }
    return internal_o;
})();
o.numbers; // [1, 2, 3], copied from the internal array
o.numbers[1] = "changed"; // Sets a value of the internal array
console.log(o.numbers[1]); // still 2
var numbers = o.numbers;
numbers[1] = "changed";
console.log(numbers[1]); // Since we are still working with the copy, this *will* have mutated

All of this comes with a performance cost, and a lot of additional code. So you should probably ask yourself how important it really is for the data to be quasi-immutable! (And someone running firebug or something similar would definitely be able to override these techniques if they wanted to -- it has full access to everything the browser does.)

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

2 Comments

Setters and getters? Why not simply set writable to false? Btw, I'd completely omit the setter, throwing an assignments to immutable data is just appropriate.
will need some time to think it trough. I like the idea with helper function that uses Object.defineProperty() function. Plus the mentioned libraries seem a good fit
0

This recursive function solves the problem, but it is not supported on mobile APIs. So yes if somebody plans to make mobile version of their app, Object.defineProperty() work on all platforms.

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.
  }
}

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze

Comments

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.