从函数返回不可变数组/对象 - JavaScript

时间:2015-04-07 13:42:36

标签: javascript arrays immutability

可能有这样的问题,但我只是想问。也许对某人有帮助。

我有这段代码:

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)));

我可以在Firebug中轻松完成此操作:

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

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

注意:

namespace(ObjectName, inheritObject, newObjectProperties);

在WML中创建一个名为ObjectName的子对象,它从inheritObject继承属性,并从newObjectProperties继承属性/方法。

如果我知道有一个Array.prototype.slice()方法可以创建调用者数组的浅层副本,那么你如何使cardLayout()返回带有不可变子数组的对象?

2 个答案:

答案 0 :(得分:1)

您可以使用setters/getters来防止意外写入应该是不可变的对象:

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

我们将数据存储在闭包中,因此无法直接访问它。你必须(递归地)为你希望不可变的每个属性执行此操作 - 你可能想要概括代码以便你可以调用类似的函数defineImmutableProperty(obj, "propertyName", value)。在您的示例中,如果将'4'属性设置为不可变,您仍然可以更改数组的元素,或者这些点的xy属性

如果你想让数组不可变,你可以这样做:

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

所有这些都带来了性能成本和许多其他代码。所以你应该问问自己,真正对于数据准不可变是多么重要啊! (如果他们想要的话,运行firebug或类似东西的人肯定能够覆盖这些技术 - 它可以完全访问浏览器所做的一切。)

答案 1 :(得分:0)

此递归函数可解决此问题,但移动API不支持此功能。所以,如果有人计划制作他们的应用程序的移动版本,Object.defineProperty()可以在所有平台上运行。

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