ES5 尽可能实现 WeakMap 的方式
见 https://github.com/drses/weak-map 。
Readme 最后提到这个库是从 13 年前由 TC39 委员会成员 Mark Miller 的工作开始的。
当然啦,在 ES5 里完全复原 WeakMap 是不可能的,所以这个工作非常 hacky 地工作。
想法
想法是非常智慧而简洁的,具体来说就是在 wm.set(target, value) 的时候,我们把 value 挂到 target 身上,随后在 wm.get(target) 的时候去 target 身上查即可,这样全程这个数据结构都没有拿到 target 的引用,自然也就不影响 GC 啦。
局限和痛点
当现代的 WeakMap 自己被回收的时候,所有的 value 也是一起回收的。但是上述方法因为把属性挂在对象上了,自然也就无法回收 value 了,这会造成轻微内存泄露。
此外,如果对象是被 freeze 的,那么就几乎束手无策了。同样的麻烦出属性枚举的时候,如何防止用户以任何方式感知到这个 value。
解决方案
为了搞定上面的问题,他们爆改了 Object.defineProperty, Object.getOwnPropertyNames, Object.isExtensible, Object.freeze, and Object.seal。
好吧其实大概能猜到在做什么,大家看段代码就知道了
var gopn = Object.getOwnPropertyNames; // 原始函数
defProp(Object, 'getOwnPropertyNames', {
// 把 Object 的函数换了
value: function fakeGetOwnPropertyNames(obj) {
return gopn(obj).filter(isNotHiddenName); // 省略掉 Hidden 的
}
});
类似的还有下面几个
(function(){
var oldFreeze = Object.freeze;
defProp(Object, 'freeze', {
value: function identifyingFreeze(obj) {
getHiddenRecord(obj);
return oldFreeze(obj);
}
});
var oldSeal = Object.seal;
defProp(Object, 'seal', {
value: function identifyingSeal(obj) {
getHiddenRecord(obj);
return oldSeal(obj);
}
});
var oldPreventExtensions = Object.preventExtensions;
defProp(Object, 'preventExtensions', {
value: function identifyingPreventExtensions(obj) {
getHiddenRecord(obj);
return oldPreventExtensions(obj);
}
});
})();
其他
Readme 里强调了一个很有趣的部分:Firefox 早期 WeakMap 草案 FF6.0a1 的缺陷。
他们的 WeakMap.prototype 一开始本身也是一个可以读写的 WeakMap 实例。
这个听起来没啥大不了的,但是这提供了 ambient mutability(环境可变性)和 ambient communications channel(隐蔽通信信道),这两个词在 Readme 里有。
具体来说是这样的,在 安全的 JS 沙箱环境 中(比如让两个互不信任的第三方脚本在同一个环境运行),有一个核心原则:如果我(宿主)没有明确把一个共享对象的引用传给你们俩,你们俩就绝对不能互相通信。
但是 Weakmap 这玩意本身是全局的,所以如果它 prototype 是一个能读写的玩意,那就会破坏这个沙箱隔离的原则。

浙公网安备 33010602011771号