yorkie / me

CV at Github and Notes based on Issues
71 stars 12 forks source link

2015-12-13 #21

Open yorkie opened 8 years ago

yorkie commented 8 years ago

旅行的意义在于敞开自己的心扉,去接纳别人看似奇怪却合其理的想法,并相互尊重,成为朋友

WeakMap与弱引用(Weak Reference)

今天在改 Fibula.js 代码的时候,我决定引入一个内部的对象来缓存每次都要运行的一个结果集,于是我打算使用WeakMap来试试,不过结果却得到了下面的错误:

TypeError: Invalid value used as weak map key
    at WeakMap.set (native)

于是我就纳闷了,我明明是这样使用的:

let dirs = new WeakMap();
// balabala...
dirs.set('name', []);

于是我先是去查v8的代码,我很快就定位到错误是在src/js/weak-collection.js的第70行处:

function WeakMapSet(key, value) {
  if (!IS_WEAKMAP(this)) {
    throw MakeTypeError(kIncompatibleMethodReceiver,
                        'WeakMap.prototype.set', this);
  }
  if (!IS_SPEC_OBJECT(key)) throw MakeTypeError(kInvalidWeakMapKey); // 就是此处
  return %WeakCollectionSet(this, key, value, GetHash(key));
}

然后我就有个疑问,为什么只有当key的值是对象(Object)才可以呢,后来我又查了下MDN,得到如下解释:

The key in a WeakMap is held weakly. What this means is that, if there are no other strong references to the key, then the entire entry will be removed from the WeakMap by the garbage collector.

读到这里,我明白了为什么会报错,但是看到什么"strong reference"什么的,我整个人还是不好的,为了弄明白为什么要这么设计,我继续查资料,于是找了下面这段代码:

Counter counter = new Counter(); // strong reference - line 1 
WeakReference<Counter> weakCounter = new WeakReference<Counter>(counter); 
//weak reference 
counter = null; // now Counter object is eligible for garbage collection

我就恍然大悟,于是我有了这样的对弱引用的解释:

弱引用即 WeakMap 中的键(Key)是指向一个对象(Object),但是这个引用并不会在垃圾回收器判断对象是否被回收时影响其结果,也就是说弱引用(Weak Reference)是一个单向引用。

这样一种特性被用在键值对(Key/Value)真是一种比较内存友好的实现方式,比如:

var map = new WeakMap();
function init(map) {
  var key = new String('this is a key');
  map.set(key, '任意值');
}
init(map);

上面的代码,在我们调用完init函数之后,由于key是在函数内定义的,所以当函数结束,尽管我们在一个全局作用域下的WeakMap中引用了它,但由于是弱引用(Weak Reference),所以key最后还是会被回收掉。

如果上面的代码,我们换成是{}或者Map,那么key无疑会被保护,一直到map先被回收为止。所以这类引用方式其实就是通过代码进行key的约定,并且对key没有特别的要求。

然后我又回过头去看了一下WeakMapMap的API,并进行了对比,发现Map相较于前者,多了Map.prototyoe.entries(类似于Object.keys),因此我们也不能在WeakMap对象上进行遍历操作了。

WeakMap特别适合这类应用:当我有一个需要长时间维护的键值对集合,这个集合会被在程序多处使用,然后再各自使用完后,可以放心地新建对象作为键(Key),而不用担心由于循环引用而造成相关对象一直无法释放的情况了。

所以,原来一直说的 Node.js 不适合维护长周期变量的最佳实践应该也不复存在了。

jinCN commented 8 years ago

其实就是js的字符串字面量不是object,而weak是对object垃圾收集做手脚,所以不匹配。归根结底是语言设计问题。c#或java也有弱引用,传字符串字面量就没问题。

jinCN commented 8 years ago

另外,string在使用被自动ToObject是约定俗成的东西,之所以weakmap实现中不这样,是因为基本类型虽然有对应的object形式,但设计上无共享语义(使得实现上可以共享而进行优化),你绕过了这个设计限制而已。简单讲:a=new String('abc'); b=a;则显然b与a指向同一片内存,但a=new String('abc'); b=new String('abc'); 则b与a是否指向同一片内存是未定义的。另外实在没必要用weakmap防止string这种小东西无法被回收,因为weakmap内部会根据这个string的地址生成另一个更复杂的标识对象,那个内存和cpu的overhead比起用map完全是倒退~

yorkie commented 8 years ago

好的吧~