我正在寻找一种冻结原生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地图做到这一点?
答案 0 :(得分:9)
没有,你可以写一个包装来做到这一点。 Object.freeze
锁定对象的属性,但Map
实例是对象,它们存储的值不是属性,因此冻结对它们没有影响,就像任何其他隐藏了内部状态的类一样。 / p>
在真正的ES6环境中,支持扩展内置版(非Babel ),您可以这样做:
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
类。
答案 1 :(得分:6)
@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);
}
答案 2 :(得分:1)
由于 Map 和 Set 对象将其元素存储在内部插槽中,因此冻结它们不会使它们不可变。 无论用于扩展或修改 Map 对象的语法如何,其内部插槽仍然可以通过 Map.prototype.set 可变。因此,保护地图的唯一方法是不要将其直接暴露给不受信任的代码。
您可以创建一个新的类似 Map 的对象,以显示您的 Map 的只读视图。例如:
function mapView (map) {
return Object.freeze({
get size () { return map.size; },
[Symbol.iterator]: map[Symbol.iterator].bind(map),
clear () { throw new TypeError("Cannot mutate a map view"); } ,
delete () { throw new TypeError("Cannot mutate a map view"); },
entries: map.entries.bind(map),
forEach (callbackFn, thisArg) {
map.forEach((value, key) => {
callbackFn.call(thisArg, value, key, this);
});
},
get: map.get.bind(map),
has: map.has.bind(map),
keys: map.keys.bind(map),
set () { throw new TypeError("Cannot mutate a map view"); },
values: map.values.bind(map),
});
}
关于这种方法需要记住以下几点:
mapView
函数。Map.prototype
方法。由于该对象不是具有内部插槽的实际 Map,因此对其应用 Map 方法会抛出异常。或者,可以将 MapView
定义为具有私有 #map
字段的类。这使得调试更容易,因为开发工具可以让您检查地图的内容。
class MapView {
#map;
constructor (map) {
this.#map = map;
Object.freeze(this);
}
get size () { return this.#map.size; }
[Symbol.iterator] () { return this.#map[Symbol.iterator](); }
clear () { throw new TypeError("Cannot mutate a map view"); }
delete () { throw new TypeError("Cannot mutate a map view"); }
entries () { return this.#map.entries(); }
forEach (callbackFn, thisArg) {
this.#map.forEach((value, key) => {
callbackFn.call(thisArg, value, key, this);
});
}
get (key) { return this.#map.get(key); }
has (key) { return this.#map.has(key); }
keys () { return this.#map.keys(); }
set () { throw new TypeError("Cannot mutate a map view"); }
values () { return this.#map.values(); }
}
与其简单地允许创建只读视图,我们还可以创建自己的 FreezableMap
类型,其 set
、delete
和 clear
方法仅有效如果对象没有被冻结。它需要做更多的工作,但结果更加灵活,因为它还让我们支持 Object.seal
和 Object.preventExtensions
。
关闭版本:
function freezableMap(...args) {
const map = new Map(...args);
return {
get size () { return map.size; },
[Symbol.iterator]: map[Symbol.iterator].bind(map),
clear () {
if (Object.isSealed(this)) {
throw new TypeError("Cannot clear a sealed map");
}
map.clear();
},
delete (key) {
if (Object.isSealed(this)) {
throw new TypeError("Cannot remove an entry from a sealed map");
}
return map.delete(key);
},
entries: map.entries.bind(map),
forEach (callbackFn, thisArg) {
map.forEach((value, key) => {
callbackFn.call(thisArg, value, key, this);
});
},
get: map.get.bind(map),
has: map.has.bind(map),
keys: map.keys.bind(map),
set (key, value) {
if (Object.isFrozen(this)) {
throw new TypeError("Cannot mutate a frozen map");
}
if (!Object.isExtensible(this) && !map.has(key)) {
throw new TypeError("Cannot add an entry to a non-extensible map");
}
map.set(key, value);
return this;
},
values: map.values.bind(map),
};
}
课程版本:
class FreezableMap {
#map;
constructor (...args) {
this.#map = new Map(...args);
}
get size () { return this.#map.size; }
[Symbol.iterator] () { return this.#map[Symbol.iterator](); }
clear () {
if (Object.isSealed(this)) {
throw new TypeError("Cannot clear a sealed map");
}
this.#map.clear();
}
delete (key) {
if (Object.isSealed(this)) {
throw new TypeError("Cannot remove an entry from a sealed map");
}
return this.#map.delete(key);
}
entries () { return this.#map.entries(); }
forEach (callbackFn, thisArg) {
this.#map.forEach((value, key) => {
callbackFn.call(thisArg, value, key, this);
});
}
get (key) { return this.#map.get(key); }
has (key) { return this.#map.has(key); }
keys () { return this.#map.keys(); }
set (key, value) {
if (Object.isFrozen(this)) {
throw new TypeError("Cannot mutate a frozen map");
}
if (!Object.isExtensible(this) && !this.#map.has(key)) {
throw new TypeError("Cannot add an entry to a non-extensible map");
}
this.#map.set(key, value);
return this;
}
values () { return this.#map.values(); }
}
答案 3 :(得分:0)
对不起,我无法发表评论。我只想添加我的打字稿变体
const mapSet = function (key: unknown) {
throw "Can't add property " + key + ', map is not extensible';
};
const mapDelete = function (key: unknown) {
throw "Can't delete property " + key + ', map is frozen';
};
const mapClear = function () {
throw 'Can\'t clear map, map is frozen';
};
function freezeMap<T extends Map<K, V>, K, V>(myMap: T) {
myMap.set = mapSet;
myMap.delete = mapDelete;
myMap.clear = mapClear;
Object.freeze(myMap);
return myMap;
}
答案 4 :(得分:0)
所以应用 ES6 让代码看起来更清晰。从我的角度来看:)
class FreezeMap extends Map {
/**
* @param {Map<number, ErrorInstance>} OriginalMap
* @return {Map<number, ErrorInstance>}
*/
constructor(OriginalMap) {
super();
OriginalMap.set = this.set.bind(OriginalMap);
OriginalMap.delete = this.delete.bind(OriginalMap);
OriginalMap.clear = this.clear.bind(OriginalMap);
Object.freeze(OriginalMap);
return OriginalMap;
};
set(key) {
throw new Error(`Can't add property ${key}, map is not extensible`);
};
delete(key) {
throw new Error(`Can't delete property ${key}, map is frozen`);
};
clear() {
throw new Error(`Can't clear map, map is frozen`);
};
}