问题 有没有办法冻结ES6地图?


我正在寻找一种方法来冻结原生ES6地图。

Object.freeze 和 Object.seal 似乎不起作用:

let myMap = new Map([["key1", "value1"]]);
// Map { 'key1' => 'value1' }

Object.freeze(myMap);
Object.seal(myMap);

myMap.set("key2", "value2");
// Map { 'key1' => 'value1', 'key2' => 'value2' }

这是预期的行为,因为冻结冻结的属性 objects 和 maps 不是 objects 或者这可能是一个错误/尚未实现?

是的,我知道,我应该使用 Immutable.js,但有没有办法用本机ES6地图做到这一点?


2308
2018-03-02 12:30


起源

有关: 有没有办法让Object.freeze()成为一个JavaScript日期? - Bergi
(我首先认为你的问题是重复的,但是在我没有搜索问题之前找不到任何问题 Map) - Bergi


答案:


@loganfsmyth,你的回答给了我一个想法,这个怎么样:

function freezeMap(myMap){

  if(myMap instanceof Map) {

    myMap.set = function(key){
      throw('Can\'t add property ' + key + ', map is not extensible');
    };

    myMap.delete = function(key){
      throw('Can\'t delete property ' + key + ', map is frozen');
    };

    myMap.clear = function(){
      throw('Can\'t clear map, map is frozen');
    };
  }

  Object.freeze(myMap);
}

这对我来说很完美:)


更新了点 @Bergi 在评论中:

var mapSet = function(key){
  throw('Can\'t add property ' + key + ', map is not extensible');
};

var mapDelete = function(key){
  throw('Can\'t delete property ' + key + ', map is frozen');
};

var mapClear = function(){
  throw('Can\'t clear map, map is frozen');
};

function freezeMap(myMap){

  myMap.set = mapSet;
  myMap.delete = mapDelete;
  myMap.clear = mapClear;

  Object.freeze(myMap);
}

6
2018-03-03 15:31



看起来很好,虽然我省略了 instanceof 你打电话给它检查 freezeMap 已经。您可以在外部缓存方法(用于极小的内存空间改进),而不是为每次调用重新创建它们。当然,它仍然具有与洛根解决方案相同的“缺陷”,而不是阻止 Map.prototype.clear.call(myMap)。这些都不重要,但你问我的想法:-) - Bergi
公平点!一直在寻找改进,谢谢:)。是的,那个“缺陷”仍然存在,但这对我来说并不重要,因为我只会将它用于测试。 - Tieme
mapClear 应该没有参数。 - Smartkid
难道你不能通过这个 Map.prototype.set.call(myMap, 'key', 'value')?我测试过,你可以用这种方式通过你的冻结功能。要实际制作冻结视图,您需要返回一个对象包装器,它只通过捕获引用底层映射,并仅提供读取方法。 - binki


没有,你可以写一个包装来做到这一点。 Object.freeze 锁定对象的属性,但同时 Map 实例是对象,它们存储的值不是属性,因此冻结对它们没有影响,就像任何其他隐藏了内部状态的类一样。

在支持扩展内置的真实ES6环境中(不是巴别塔),你可以这样做:

class FreezableMap extends Map {
    set(...args){
        if (Object.isFrozen(this)) return this;

        return super.set(...args);
    }
    delete(...args){
        if (Object.isFrozen(this)) return false;

        return super.delete(...args);
    }
    clear(){
        if (Object.isFrozen(this)) return;

        return super.clear();
    }
}

如果您需要在ES5环境中工作,您可以轻松地为一个包装类 Map 而不是扩展 Map 类。


6
2018-03-02 16:56



......但当然这不会阻止任何人这样做 Map.prototype.clear.call(supposedlyFrozenMap) - Bergi
@Bergi是的,我假设目标是避免意外突变,而不是真正的冻结。你需要为它做包装类。 - loganfsmyth
目的确实避免了测试期间的意外突变。但确实使用babel ..将再看看是否可以将整个代码库迁移到不可变的。谢谢! - Tieme
虽然我喜欢静默返回的想法,但最好模仿原始的返回类型,所以 this for set(), false (?)for delete()和 undefined 为了清楚。 - ccprog
我建议使用isExtensible,而不是isFrozen。虽然冻结似乎是表面上更好的概念匹配,但[[MapData]]的“伪属性”永远不能参与像TestIntegrityLevel这样的算法 - 至少[[IsExtensible]]只涉及布尔标志。考虑: Object.isFrozen(Object.seal(new Map([ [ 1, 2 ] ])))是的,但是 Object.isFrozen(Object.seal(Object.assign(new Map([ [ 1, 2 ] ]), { x: 1 }))) 是假的。冻结/密封主要描述属性描述符的状态这一事实使得尝试以这种方式应用它们变得有些奇怪。 - Semicolon