[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"content-usage_of_weakmap":3,"$f1wqjvx89uubl3":25},{"id":4,"type":5,"slug":6,"title":7,"date":8,"category":9,"tags":10,"body_markdown":16,"permalink":17,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"html":22,"excerpt":23,"cover":24},179,"article","usage_of_weakmap","ES2015 WeakMap的学习和使用","2017-02-27 13:15:00","web",[11,12,13,14,15],"JavaScript","ES2015","ES6","WeakMap","数据结构","\nES2015（ES6）中新增了几种数据类型，包括Map WeakMap Set WeakSet等。其中Map可以与我们熟悉的对象Object进行对照，他们的功能都是提供一个键值对集合，主要的区别在于Object的key只能是字符串，而Map的key可以是任意类型。关于Map的大致用法可以参考[MDN](https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FMap)，我在前一篇文章[《也谈JavaScript数组去重》](https:\u002F\u002Fwww.toobug.net\u002Farticle\u002Farray_unique_in_javascript.html)中也有提及。\n\n今天要讨论的主角是WeakMap。\n\n按照[MDN](https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FWeakMap)上的说明\n\n> WeakMap 对象是键\u002F值对的集合，且其中的键是弱引用的。其键只能是对象，而值则可以是任意的。\n\n从这段描述来看，我们可以大致推断出，WeakMap与Map的主要区别在于两点：\n\n1. WeakMap对key的引用是弱引用\n2. WeakMap的key只能是对象\n\n这两点意味着什么呢？反正我第一眼看到的时候不是拉格良日懵就是三元二次懵的状态。于是围绕WeakMap去查阅了一些资料，渐渐地有了一些更深入的认识，记录成本文。\n\n\u003C!-- more -->\n\n## WeakMap的特性\n\n具体而言，WeakMap大致有如下一些明显的特性：\n\n### 1. key只能使用对象\n\n例如：\n\n```javascript\nvar m = new WeakMap();\nvar k = {};\n\n\u002F\u002F 设置键值对\nm.set(k, 1);\n\n\u002F\u002F 取值\nm.get(k);   \u002F\u002F1\n\n\u002F\u002F 非对象的key会报错\nm.set('k', 1);  \u002F\u002F报错\n```\n\n在上例中，我们使用了对象`k`作为WeakMap的key，设置了value为`1`。到下面取值的时候，只能使用同一个对象`k`去取。也就是说WeakMap是按照key的引用来对value进行存取的。\n\n> 关于“key只能使用对象”和“value的查找是通过比较key的引用”这两个命题，其实是互为因果的，本质上是一个先有鸡还是先有蛋的问题：\n>\n> 正因为WeakMap只能使用对象作为key，所以取值的时候对key进行查找也只能按对象引用进行查找。\n>\n> 正因为WeakMap在查找的时候只能按对象引用进行查找，所以只能使用对象作为key，否则存进去的值根本无法查找取出。\n\n### 2. key中的对象保持弱引用\n\n弱引用正是WeakMap中“Weak”的含义。熟悉JavaScript的朋友都知道引用是怎么回事，简单地说，当一个对象被引用的时候，往往意味着它正在被使用，或者在将来有可能会被使用。此时对象不会被垃圾回收机制回收掉。\n\n```javascript\nvar obj = {};\n......\nobj = null;\n```\n\n这段代码中，一开始`obj`引用了使用字面量创建的空对象，因此这个空对象不会被回收。很久以后`obj`被指向了`null`，不再引用空对象，此时这个空对象就不再能从任何代码中被访问到，将被回收掉。\n\n> 为简单起见，这里不讨论循环引用的垃圾回收问题。\n\n而弱引用则可以理解为“引用了对象，但是不影响它的垃圾回收”。假设，请注意，是假设，仅仅是假设，假设上例中第一句`obj = {}`是一个弱引用的关系，那么因为这个空对象没有其它的引用，它将很快被垃圾回收，在下方无法访问到。\n\n具体到WeakMap而言，大概是这样：\n\n```javascript\nvar obj = {};\nvar wm = new WeakMap();\nwm.set(obj, 1);\nwm.get(obj);\t\u002F\u002F 1\n......\nobj = null;\nwm.get(obj);\t\u002F\u002F 这句没有意义\n```\n\n在这个例子中，WeakMap实例`wm`（弱）引用了`obj`对象（空对象），接着下方代码释放了对空对象的引用（`obj = null`），此时和上例一样，空对象将被垃圾回收。也即`wm`中持有的空对象（弱）引用并不影响对对象本身的垃圾回收。这就是WeakMap中“弱引用”的含义。\n\n值得注意的是最后一行代码（`wm.get(obj)`），因为`obj`的引用已经被修改，所以这里无法访问到原来`obj`关联的值`1`，同时，会不会因为空对象已经被垃圾回收，所以`wm`中其实也已经没有了这个值。那么，到底是因为我们找不到路所以取不到值，还是因为它的值本来就不存在了呢？答案应该是Both吧，不过这个问题不翻规范的话，有点像哲学问题了，挺好玩。\n\n### 3. 无法遍历\n\nWeakMap和Map\u002FObject等有一个很大的不同，即它是不可遍历的。你无法使用`for...in`或者`for...of`等语句知道WeakMap中的内容。\n\n至于具体的原因……其实我不知道。MDN上是这样介绍的：\n\n> 正由于这样的弱引用，WeakMap 的 key 是非枚举的 (没有方法能给出所有的 key)。如果key 是可枚举的话，其列表将会受垃圾回收机制的影响，从而得到不确定的结果.\n\n从这段描述中，可以大致验证下我们上面的问题，当对象引用消失后，到底是因为我们缺少了引用所以无法从WeakMap中取到值还是WeakMap中的值本身也会消失？稍微了解一些JS垃圾回收知识的朋友都清楚，JS的垃圾回收会在一些条件（剩余内存、CPU负荷情况）下触发，也就是说可能会有一定延时。而上文说如果可以遍历，结果会受垃圾回收机制影响，大致可以理解为：如果可以遍历，那么在垃圾回收之前，将遍历到已经没有引用的对象和对应的值，在垃圾回收之后，则对象和值一起消失。因此可以大概猜出结论：WeakMap中的值会在垃圾回收时才消失。\n\n## WeakMap的使用场景\n\n客观地说，WeakMap的使用场景并不是很多，而且在条件不是非常苛刻的前提下，一般都可以有替代解决方案。不过很多情况下使用WeakMap来解决问题会更简单可靠一些。\n\n### 一个例子\n\n看完前面又臭又长的理论，不知道你会不会已经急得抓耳挠腮了：这么个key只能是对象，又不能遍历的东东，到底有什么用啊？反正我当时是急得想跺脚了，几乎所有的文章都只说这是个什么东西，并不告诉你它有什么用。经过多方求证，最后终于了解到它的核心思想：\n\n**在不改变对象本身的情况下扩展对象。**\n\n怎么理解呢？假如有100只鸡，现在要对每只鸡称重并记录。那么，鸡的体重记录到哪里就成了一个问题。我们有两种选择：\n\n1. 记录到一个本本上\n2. 想办法用笔写到鸡身上\n\n```javascript\n\u002F\u002F 1只鸡\nvar chicken = new Chicken();\n\n\u002F\u002F 100只鸡\nvar chickenList = [chicken, xxx, ...];\n\n\u002F\u002F 方法1:记录到本本上\nvar notebook = [];\nchickenList.forEach(function(chickenItem, index){\n\tnotebook[index] = getWeight(chickenItem);\n});\n\n\u002F\u002F 方法2:记录到鸡身上\nchickenList.forEach(function(chickenItem, index){\n\tchickenItem.weight = getWeight(chickenItem);\n});\n```\n\n首先我们看一下第2种方法，记录到鸡身上。这种方法的好处在于我们不需要额外的笔记本（变量）。但同时它也有很明显的缺点：\n\n1. 破坏了鸡的卖相，有时候这是很严重的事情，比如你想把一只5斤的鸡当成6斤卖出去，结果鸡身上直接写“我只有5斤”（修改了原有对象，可能导致意外的行为）\n2. 可能碰到一些战斗鸡，一个字都写不上去（对象冻结了或者有不可覆盖的属性）\n3. 可能写到一些本来就写了字的地方，导致根本看不清（与对象原有属性冲突）\n4. 如果鸡的生产线上有光学设备，有可能破坏生产线的生产，因为这只鸡变成了一只非标品，只能使用人工生产，降低效率（破坏JS引擎的hidden class优化机制）\n\n我们再来看一下第1种方法。这种方法的好处在于完全不用改动原来的对象，但它有比较明显的问题：\n\n1. 需要一个专门的本本来记录结果（额外变量）\n2. 本本无法和鸡精准地一一对应，只能靠一些索引或者标记（例如给每只鸡起一个名字）去（不可靠）地记录对应关系（无法精准地对比到是哪一个对象）\n3. 本本上的结果可以随时被别人修改\n\n针对上述第2个问题，一般我们在日常开发中，都会给对象加上一些标识，例如`id`属性，去与`notebook`记录的数据做一一对应。这也是我们在处理这一类需求的时候，一般不会觉得有困扰的原因。但这只适用于你能控制`chicken`对象结构的情况。如果你并不知道你要处理是一个什么样的对象，那么这种方法就会面临上方提到的第2个问题。\n\n这样的需求用Map是否可以解决呢？答案是肯定的：\n\n```javascript\n\u002F\u002F 1只鸡\nvar chicken = new Chicken();\n\n\u002F\u002F 100只鸡\nvar chickenList = [chicken, xxx, ...];\n\n\u002F\u002F 记录到另一个本本上\nvar notebook = new Map();\nchickenList.forEach(function(chickenItem, index){\n\tnotebook.set(chickenItem, getWeight(chickenItem));\n});\n```\n\n这里将本本`notebook`换成了一个Map，而Map可以保留对`chicken`的引用，从而解决上面说的使用数组或者对象来记录时无法精准对应的问题。\n\n这里用Map解决了上述第2个问题，但仍然存在两个问题：\n\n1. 需要额外变量`notebook`来存储所有的重量数据\n2. `notebook`中的数据可能随时被别人修改\n\n此时，我们终于可以看看WeakMap在这个问题上是如何表现的了：\n\n```javascript\n\u002F\u002F 1只鸡\nvar chicken = new Chicken();\n\n\u002F\u002F 100只鸡\nvar chickenList = [chicken, xxx, ...];\n\n\u002F\u002F 记录到WeakMap\nvar notebook = new WeakMap();\nchickenList.forEach(function(chickenItem, index){\n\tnotebook.set(chickenItem, getWeight(chickenItem));\n});\n```\n\n咦？怎么感觉跟Map的例子一样一样的？没错。Map和WeakMap的作用和用法非常相似。但是因为WeakMap弱引用和不可遍历，这里有一些不同的事情：\n\n1. 当你拿到`chicken`引用的时候，获取重量并记录到WeakMap `notbook`中，但是一旦释放`chicken`，则`notebook`中对应的数据也无法再访问，这可以很好地节省内存\n2. 因为`notebook`不可遍历，也就意味着你只有在需要使用`chicken`（有引用）时，才可以访问`notebook`，可以有效防止被外部修改\n\n打个比方，你的本本上，关于这只鸡的记录，只有当鸡来了的时候才存在，而且只有鸡在场的时候才能查到，当鸡走了，在本本上关于鸡的记录是不存在的，也就不存在被别人偷看、修改一说。也就是说，这个本本上关于鸡的记录好像根本就是这只鸡自己带来的一样。这就是上方所说的**在不改变对象本身的情况下扩展对象**的含义。\n\n同样的原理和写法，可以应用到其它的场景中，比如标记对象的状态（用于任务调度、错误处理等），比如为DOM元素添加额外的关联数据等等。\n\n### 应用1：事件系统\n\n在Node中，如果我们要使用事件系统的话，一般会将让自己的Class（构造函数）继承`EventEmitter`。而如果要为任意对象添加事件机制的话，就不那么容易了。有了`WeakMap`，就可以比较容易地处理这件事情了：\n\n```javascript\nvar listeners = new WeakMap();\n\n\u002F\u002F 监听事件\nfunction on(object, event, fn){\n\tvar thisListeners = listeners.get(object);\n\tif(!thisListeners) thisListeners = {};\n\tif(!thisListeners[event]) thisListeners[event] = [];\n\tthisListeners[event].push(fn);\n\tlisteners.set(object, thisListeners);\n}\n\n\u002F\u002F 触发事件\nfunction emit(object, event){\n\tvar thisListeners = listeners.get(object);\n\tif(!thisListeners) thisListeners = {};\n\tif(!thisListeners[event]) thisListeners[event] = [];\n\tthisListeners[event].forEach(function(fn){\n\t\tfn.call(object, event);\n\t});\n}\n\n\u002F\u002F 使用\nvar obj = {};\n\non(obj, 'hello', function(){\n\tconsole.log('hello');\n});\n\nemit(obj, 'hello');\n```\n\n### 应用2：私有变量\n\n```javascript\nfunction Constructor() {\n    var data = new WeakMap();\n\n \t\u002F\u002F 重写构建函数\n    Constructor = function() {\n    \t\u002F\u002F 挂一个私有变量存储\n        data.set(this, {});\n    }\n\n \t\u002F\u002F 方法\n    Constructor.prototype.doSth = function () {\n    \tvar privateVar = data.get(this);\n    \t......\n    };\n\n    return new Constructor();\n};\n```\n\n我们使用`data`来扩展`this`对象，用来存储私有变量，这个私有变量在外部无法被访问，而且随`this`对象的销毁和消失，简直完美。\n\n你可能会说，私有变量不是都已经有现成的方案了吗？\n\n```javascript\nfunction Constructor() {\n    var data = {};\n\n \t\u002F\u002F 方法\n    this.doSth = function () {\n    \tvar privateVar = data;\n    \t......\n    };\n};\n```\n\n没错，这正是经典的私有变量解决方案。但是这个方案有一个问题，即所有访问私有变量的方法（如`doSth()`）都只能挂在实例上，即每个实例中除了私有变量外，还有自己的特权方法。而使用WeakMap实现私有变量的方案中，方法可以挂在原型上。这两者在性能上会有一些差异。\n\n> ES2015虽然有Class，但是仍然不支持私有变量的定义，要使用私有变量的话，实现方法和ES5没有太大差异。\n\n## WeakMap的模拟\n\n如果你有关注过ES2015的特性在ES5中的模拟降级情况的话，应该会发现很多人说过，WeakMap是无法模拟的。事实的确是这样，但是如果我们稍微放宽一些条件，还是有办法模拟出一个可用的WeakMap的。\n\n首先，我们看一个[web components polyfill](https:\u002F\u002Fgithub.com\u002Fwebcomponents\u002Fwebcomponentsjs)中[对WeakMap的模拟](https:\u002F\u002Fgithub.com\u002Fwebcomponents\u002Fwebcomponentsjs\u002Fblob\u002Fmaster\u002Fsrc\u002FWeakMap\u002FWeakMap.js)：\n\n```javascript\nif (typeof WeakMap === 'undefined') {\n  (function() {\n    var defineProperty = Object.defineProperty;\n    var counter = Date.now() % 1e9;\n\n    var WeakMap = function() {\n      \u002F\u002F 记录一个唯一的name属性\n      this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');\n    };\n\n    WeakMap.prototype = {\n      \u002F\u002F 在WeakMap的key对象中添加与WeakMap实例name相同的属性\n      \u002F\u002F 利用这个属性来保存value\n      \u002F\u002F 下面的API原理类似\n      set: function(key, value) {\n        var entry = key[this.name];\n        if (entry && entry[0] === key)\n          entry[1] = value;\n        else\n          defineProperty(key, this.name, {value: [key, value], writable: true});\n        return this;\n      },\n      get: function(key) {\n        var entry;\n        return (entry = key[this.name]) && entry[0] === key ?\n            entry[1] : undefined;\n      },\n      delete: function(key) {\n        var entry = key[this.name];\n        if (!entry || entry[0] !== key) return false;\n        entry[0] = entry[1] = undefined;\n        return true;\n      },\n      has: function(key) {\n        var entry = key[this.name];\n        if (!entry) return false;\n        return entry[0] === key;\n      }\n    };\n\n    window.WeakMap = WeakMap;\n  })();\n}\n```\n\n上面的代码中我们加了一点注释，这个模拟的核心在于，`weakMap.set(key, value)`时，将`value`直接写入`key`这个对象中，作为对象的一个属性。因此它的生命周期与对象本身完全一致，也不会影响对象的垃圾回收，基本达到WeakMap的核心诉求。\n\n而它也有一些不足之处，最大的问题就是会修改对象本身。我们前文说过，WeakMap的核心思想是“在不改变对象本身的情况下扩展对象”，而我们的模拟却刚好违背了这一思想，将值扩展到了对象本身。如上文所说，总有一些对象是不可修改的，在这种情况下就会出现问题。这也是刚刚提到的必须在放宽一些条件的情况下，才可以模拟WeakMap的意思。\n\n基于同样的原理，还有一个模拟WeakMap的库\u003Chttps:\u002F\u002Fgithub.com\u002FBenvie\u002FWeakMap>。这个库的源码可见这里\u003Chttps:\u002F\u002Fgithub.com\u002FBenvie\u002FWeakMap\u002Fblob\u002Fmaster\u002Fweakmap.js>，它将数据以及对数据的操作封装到了`Data()`中，然后将它挂到了对象的一个属性`globalId`上。详细的关系大概如下：\n\n```\nobject:{\n\t[globalId]: data{\n\t\t[puid1]: value1\n\t\t[puid2]: value2\n\t}\n}\n```\n\n值得一提的是，`globalId`是全局唯一的，也就是说在一次脚本运行的过程中（不关掉页面，或者Node进程不退出），所有对象上的`globalId`都是相同的。\n\n相比web components的模拟而言，这个库的模拟要严谨细心得多，比如当对象不可写时会抛出错误，比如在产生随机ID时会进行重复判断，确保逻辑正确，比如它只在对象上写入`[globalId]`一个属性，尽量减少对对象的修改。当然这些逻辑也导致了代码可读性直线下降，解读这段代码花了我整整一个晚上的时间，有兴趣的同学可以自己看一下是否能够读懂。\n\n## 结\n\n本文简单总结了下自己在学习WeakMap过程中记录的相关知识点，包括WeakMap的特性、使用场景以及模拟实现。\n\n总体说来，WeakMap是一个有独特特性和使用场景的数据类型，它的出现使我们能更从容地应对一些开发过程中的问题。但话又说回来，在WeakMap出现之前，同样的问题我们也一样能解决，只是可能稍微麻烦一些，或者性能稍微差一些。\n\n另，本文所使用比喻仅为说明问题而设，各位看官明白要表达的意思即可，勿在比喻中钻牛角尖。\n\n参考资料：\n\n- \u003Chttps:\u002F\u002Fsanwen8.cn\u002Fp\u002F1e9XKBe.html>\n- \u003Chttps:\u002F\u002Fwww.sitepen.com\u002Fblog\u002F2015\u002F03\u002F19\u002Flegitimate-memory-efficient-privacy-with-es6-weakmaps\u002F>\n- \u003Chttp:\u002F\u002Fwww.2ality.com\u002F2016\u002F01\u002Fprivate-data-classes.html>\n- \u003Chttps:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FWeakMap>\n- \u003Chttp:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F29413222\u002Fwhat-are-the-actual-uses-of-es6-weakmap>\n- \u003Chttp:\u002F\u002Ffitzgeraldnick.com\u002F2014\u002F01\u002F13\u002Fhiding-implementation-details-with-e6-weakmaps.html>\n- \u003Chttps:\u002F\u002Filikekillnerds.com\u002F2015\u002F02\u002Fwhat-are-weakmaps-in-es6\u002F>\n- \u003Chttps:\u002F\u002Fsegmentfault.com\u002Fa\u002F1190000002549235>\n- \u003Chttps:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FWeakMap>\n","\u002Farticle\u002Fusage_of_weakmap.html",null,0,"2026-08-28 04:37:17","published","\u003Cp>ES2015（ES6）中新增了几种数据类型，包括Map WeakMap Set WeakSet等。其中Map可以与我们熟悉的对象Object进行对照，他们的功能都是提供一个键值对集合，主要的区别在于Object的key只能是字符串，而Map的key可以是任意类型。关于Map的大致用法可以参考\u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FMap\">MDN\u003C\u002Fa>，我在前一篇文章\u003Ca href=\"https:\u002F\u002Fwww.toobug.net\u002Farticle\u002Farray_unique_in_javascript.html\">《也谈JavaScript数组去重》\u003C\u002Fa>中也有提及。\u003C\u002Fp>\n\u003Cp>今天要讨论的主角是WeakMap。\u003C\u002Fp>\n\u003Cp>按照\u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FWeakMap\">MDN\u003C\u002Fa>上的说明\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>WeakMap 对象是键\u002F值对的集合，且其中的键是弱引用的。其键只能是对象，而值则可以是任意的。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>从这段描述来看，我们可以大致推断出，WeakMap与Map的主要区别在于两点：\u003C\u002Fp>\n\u003Col>\n\u003Cli>WeakMap对key的引用是弱引用\u003C\u002Fli>\n\u003Cli>WeakMap的key只能是对象\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>这两点意味着什么呢？反正我第一眼看到的时候不是拉格良日懵就是三元二次懵的状态。于是围绕WeakMap去查阅了一些资料，渐渐地有了一些更深入的认识，记录成本文。\u003C\u002Fp>\n\u003Ch2>WeakMap的特性\u003C\u002Fh2>\n\u003Cp>具体而言，WeakMap大致有如下一些明显的特性：\u003C\u002Fp>\n\u003Ch3>1. key只能使用对象\u003C\u002Fh3>\n\u003Cp>例如：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>var m = new WeakMap();\nvar k = {};\n\n\u002F\u002F 设置键值对\nm.set(k, 1);\n\n\u002F\u002F 取值\nm.get(k);   \u002F\u002F1\n\n\u002F\u002F 非对象的key会报错\nm.set('k', 1);  \u002F\u002F报错\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>在上例中，我们使用了对象\u003Ccode>k\u003C\u002Fcode>作为WeakMap的key，设置了value为\u003Ccode>1\u003C\u002Fcode>。到下面取值的时候，只能使用同一个对象\u003Ccode>k\u003C\u002Fcode>去取。也就是说WeakMap是按照key的引用来对value进行存取的。\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>关于“key只能使用对象”和“value的查找是通过比较key的引用”这两个命题，其实是互为因果的，本质上是一个先有鸡还是先有蛋的问题：\u003C\u002Fp>\n\u003Cp>正因为WeakMap只能使用对象作为key，所以取值的时候对key进行查找也只能按对象引用进行查找。\u003C\u002Fp>\n\u003Cp>正因为WeakMap在查找的时候只能按对象引用进行查找，所以只能使用对象作为key，否则存进去的值根本无法查找取出。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch3>2. key中的对象保持弱引用\u003C\u002Fh3>\n\u003Cp>弱引用正是WeakMap中“Weak”的含义。熟悉JavaScript的朋友都知道引用是怎么回事，简单地说，当一个对象被引用的时候，往往意味着它正在被使用，或者在将来有可能会被使用。此时对象不会被垃圾回收机制回收掉。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>var obj = {};\n......\nobj = null;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这段代码中，一开始\u003Ccode>obj\u003C\u002Fcode>引用了使用字面量创建的空对象，因此这个空对象不会被回收。很久以后\u003Ccode>obj\u003C\u002Fcode>被指向了\u003Ccode>null\u003C\u002Fcode>，不再引用空对象，此时这个空对象就不再能从任何代码中被访问到，将被回收掉。\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>为简单起见，这里不讨论循环引用的垃圾回收问题。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>而弱引用则可以理解为“引用了对象，但是不影响它的垃圾回收”。假设，请注意，是假设，仅仅是假设，假设上例中第一句\u003Ccode>obj = {}\u003C\u002Fcode>是一个弱引用的关系，那么因为这个空对象没有其它的引用，它将很快被垃圾回收，在下方无法访问到。\u003C\u002Fp>\n\u003Cp>具体到WeakMap而言，大概是这样：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>var obj = {};\nvar wm = new WeakMap();\nwm.set(obj, 1);\nwm.get(obj);\t\u002F\u002F 1\n......\nobj = null;\nwm.get(obj);\t\u002F\u002F 这句没有意义\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>在这个例子中，WeakMap实例\u003Ccode>wm\u003C\u002Fcode>（弱）引用了\u003Ccode>obj\u003C\u002Fcode>对象（空对象），接着下方代码释放了对空对象的引用（\u003Ccode>obj = null\u003C\u002Fcode>），此时和上例一样，空对象将被垃圾回收。也即\u003Ccode>wm\u003C\u002Fcode>中持有的空对象（弱）引用并不影响对对象本身的垃圾回收。这就是WeakMap中“弱引用”的含义。\u003C\u002Fp>\n\u003Cp>值得注意的是最后一行代码（\u003Ccode>wm.get(obj)\u003C\u002Fcode>），因为\u003Ccode>obj\u003C\u002Fcode>的引用已经被修改，所以这里无法访问到原来\u003Ccode>obj\u003C\u002Fcode>关联的值\u003Ccode>1\u003C\u002Fcode>，同时，会不会因为空对象已经被垃圾回收，所以\u003Ccode>wm\u003C\u002Fcode>中其实也已经没有了这个值。那么，到底是因为我们找不到路所以取不到值，还是因为它的值本来就不存在了呢？答案应该是Both吧，不过这个问题不翻规范的话，有点像哲学问题了，挺好玩。\u003C\u002Fp>\n\u003Ch3>3. 无法遍历\u003C\u002Fh3>\n\u003Cp>WeakMap和Map\u002FObject等有一个很大的不同，即它是不可遍历的。你无法使用\u003Ccode>for...in\u003C\u002Fcode>或者\u003Ccode>for...of\u003C\u002Fcode>等语句知道WeakMap中的内容。\u003C\u002Fp>\n\u003Cp>至于具体的原因……其实我不知道。MDN上是这样介绍的：\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>正由于这样的弱引用，WeakMap 的 key 是非枚举的 (没有方法能给出所有的 key)。如果key 是可枚举的话，其列表将会受垃圾回收机制的影响，从而得到不确定的结果.\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>从这段描述中，可以大致验证下我们上面的问题，当对象引用消失后，到底是因为我们缺少了引用所以无法从WeakMap中取到值还是WeakMap中的值本身也会消失？稍微了解一些JS垃圾回收知识的朋友都清楚，JS的垃圾回收会在一些条件（剩余内存、CPU负荷情况）下触发，也就是说可能会有一定延时。而上文说如果可以遍历，结果会受垃圾回收机制影响，大致可以理解为：如果可以遍历，那么在垃圾回收之前，将遍历到已经没有引用的对象和对应的值，在垃圾回收之后，则对象和值一起消失。因此可以大概猜出结论：WeakMap中的值会在垃圾回收时才消失。\u003C\u002Fp>\n\u003Ch2>WeakMap的使用场景\u003C\u002Fh2>\n\u003Cp>客观地说，WeakMap的使用场景并不是很多，而且在条件不是非常苛刻的前提下，一般都可以有替代解决方案。不过很多情况下使用WeakMap来解决问题会更简单可靠一些。\u003C\u002Fp>\n\u003Ch3>一个例子\u003C\u002Fh3>\n\u003Cp>看完前面又臭又长的理论，不知道你会不会已经急得抓耳挠腮了：这么个key只能是对象，又不能遍历的东东，到底有什么用啊？反正我当时是急得想跺脚了，几乎所有的文章都只说这是个什么东西，并不告诉你它有什么用。经过多方求证，最后终于了解到它的核心思想：\u003C\u002Fp>\n\u003Cp>\u003Cstrong>在不改变对象本身的情况下扩展对象。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>怎么理解呢？假如有100只鸡，现在要对每只鸡称重并记录。那么，鸡的体重记录到哪里就成了一个问题。我们有两种选择：\u003C\u002Fp>\n\u003Col>\n\u003Cli>记录到一个本本上\u003C\u002Fli>\n\u003Cli>想办法用笔写到鸡身上\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>\u002F\u002F 1只鸡\nvar chicken = new Chicken();\n\n\u002F\u002F 100只鸡\nvar chickenList = [chicken, xxx, ...];\n\n\u002F\u002F 方法1:记录到本本上\nvar notebook = [];\nchickenList.forEach(function(chickenItem, index){\n\tnotebook[index] = getWeight(chickenItem);\n});\n\n\u002F\u002F 方法2:记录到鸡身上\nchickenList.forEach(function(chickenItem, index){\n\tchickenItem.weight = getWeight(chickenItem);\n});\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>首先我们看一下第2种方法，记录到鸡身上。这种方法的好处在于我们不需要额外的笔记本（变量）。但同时它也有很明显的缺点：\u003C\u002Fp>\n\u003Col>\n\u003Cli>破坏了鸡的卖相，有时候这是很严重的事情，比如你想把一只5斤的鸡当成6斤卖出去，结果鸡身上直接写“我只有5斤”（修改了原有对象，可能导致意外的行为）\u003C\u002Fli>\n\u003Cli>可能碰到一些战斗鸡，一个字都写不上去（对象冻结了或者有不可覆盖的属性）\u003C\u002Fli>\n\u003Cli>可能写到一些本来就写了字的地方，导致根本看不清（与对象原有属性冲突）\u003C\u002Fli>\n\u003Cli>如果鸡的生产线上有光学设备，有可能破坏生产线的生产，因为这只鸡变成了一只非标品，只能使用人工生产，降低效率（破坏JS引擎的hidden class优化机制）\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>我们再来看一下第1种方法。这种方法的好处在于完全不用改动原来的对象，但它有比较明显的问题：\u003C\u002Fp>\n\u003Col>\n\u003Cli>需要一个专门的本本来记录结果（额外变量）\u003C\u002Fli>\n\u003Cli>本本无法和鸡精准地一一对应，只能靠一些索引或者标记（例如给每只鸡起一个名字）去（不可靠）地记录对应关系（无法精准地对比到是哪一个对象）\u003C\u002Fli>\n\u003Cli>本本上的结果可以随时被别人修改\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>针对上述第2个问题，一般我们在日常开发中，都会给对象加上一些标识，例如\u003Ccode>id\u003C\u002Fcode>属性，去与\u003Ccode>notebook\u003C\u002Fcode>记录的数据做一一对应。这也是我们在处理这一类需求的时候，一般不会觉得有困扰的原因。但这只适用于你能控制\u003Ccode>chicken\u003C\u002Fcode>对象结构的情况。如果你并不知道你要处理是一个什么样的对象，那么这种方法就会面临上方提到的第2个问题。\u003C\u002Fp>\n\u003Cp>这样的需求用Map是否可以解决呢？答案是肯定的：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F 1只鸡\nvar chicken = new Chicken();\n\n\u002F\u002F 100只鸡\nvar chickenList = [chicken, xxx, ...];\n\n\u002F\u002F 记录到另一个本本上\nvar notebook = new Map();\nchickenList.forEach(function(chickenItem, index){\n\tnotebook.set(chickenItem, getWeight(chickenItem));\n});\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这里将本本\u003Ccode>notebook\u003C\u002Fcode>换成了一个Map，而Map可以保留对\u003Ccode>chicken\u003C\u002Fcode>的引用，从而解决上面说的使用数组或者对象来记录时无法精准对应的问题。\u003C\u002Fp>\n\u003Cp>这里用Map解决了上述第2个问题，但仍然存在两个问题：\u003C\u002Fp>\n\u003Col>\n\u003Cli>需要额外变量\u003Ccode>notebook\u003C\u002Fcode>来存储所有的重量数据\u003C\u002Fli>\n\u003Cli>\u003Ccode>notebook\u003C\u002Fcode>中的数据可能随时被别人修改\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>此时，我们终于可以看看WeakMap在这个问题上是如何表现的了：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F 1只鸡\nvar chicken = new Chicken();\n\n\u002F\u002F 100只鸡\nvar chickenList = [chicken, xxx, ...];\n\n\u002F\u002F 记录到WeakMap\nvar notebook = new WeakMap();\nchickenList.forEach(function(chickenItem, index){\n\tnotebook.set(chickenItem, getWeight(chickenItem));\n});\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>咦？怎么感觉跟Map的例子一样一样的？没错。Map和WeakMap的作用和用法非常相似。但是因为WeakMap弱引用和不可遍历，这里有一些不同的事情：\u003C\u002Fp>\n\u003Col>\n\u003Cli>当你拿到\u003Ccode>chicken\u003C\u002Fcode>引用的时候，获取重量并记录到WeakMap \u003Ccode>notbook\u003C\u002Fcode>中，但是一旦释放\u003Ccode>chicken\u003C\u002Fcode>，则\u003Ccode>notebook\u003C\u002Fcode>中对应的数据也无法再访问，这可以很好地节省内存\u003C\u002Fli>\n\u003Cli>因为\u003Ccode>notebook\u003C\u002Fcode>不可遍历，也就意味着你只有在需要使用\u003Ccode>chicken\u003C\u002Fcode>（有引用）时，才可以访问\u003Ccode>notebook\u003C\u002Fcode>，可以有效防止被外部修改\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>打个比方，你的本本上，关于这只鸡的记录，只有当鸡来了的时候才存在，而且只有鸡在场的时候才能查到，当鸡走了，在本本上关于鸡的记录是不存在的，也就不存在被别人偷看、修改一说。也就是说，这个本本上关于鸡的记录好像根本就是这只鸡自己带来的一样。这就是上方所说的\u003Cstrong>在不改变对象本身的情况下扩展对象\u003C\u002Fstrong>的含义。\u003C\u002Fp>\n\u003Cp>同样的原理和写法，可以应用到其它的场景中，比如标记对象的状态（用于任务调度、错误处理等），比如为DOM元素添加额外的关联数据等等。\u003C\u002Fp>\n\u003Ch3>应用1：事件系统\u003C\u002Fh3>\n\u003Cp>在Node中，如果我们要使用事件系统的话，一般会将让自己的Class（构造函数）继承\u003Ccode>EventEmitter\u003C\u002Fcode>。而如果要为任意对象添加事件机制的话，就不那么容易了。有了\u003Ccode>WeakMap\u003C\u002Fcode>，就可以比较容易地处理这件事情了：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>var listeners = new WeakMap();\n\n\u002F\u002F 监听事件\nfunction on(object, event, fn){\n\tvar thisListeners = listeners.get(object);\n\tif(!thisListeners) thisListeners = {};\n\tif(!thisListeners[event]) thisListeners[event] = [];\n\tthisListeners[event].push(fn);\n\tlisteners.set(object, thisListeners);\n}\n\n\u002F\u002F 触发事件\nfunction emit(object, event){\n\tvar thisListeners = listeners.get(object);\n\tif(!thisListeners) thisListeners = {};\n\tif(!thisListeners[event]) thisListeners[event] = [];\n\tthisListeners[event].forEach(function(fn){\n\t\tfn.call(object, event);\n\t});\n}\n\n\u002F\u002F 使用\nvar obj = {};\n\non(obj, 'hello', function(){\n\tconsole.log('hello');\n});\n\nemit(obj, 'hello');\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>应用2：私有变量\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>function Constructor() {\n    var data = new WeakMap();\n\n \t\u002F\u002F 重写构建函数\n    Constructor = function() {\n    \t\u002F\u002F 挂一个私有变量存储\n        data.set(this, {});\n    }\n\n \t\u002F\u002F 方法\n    Constructor.prototype.doSth = function () {\n    \tvar privateVar = data.get(this);\n    \t......\n    };\n\n    return new Constructor();\n};\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>我们使用\u003Ccode>data\u003C\u002Fcode>来扩展\u003Ccode>this\u003C\u002Fcode>对象，用来存储私有变量，这个私有变量在外部无法被访问，而且随\u003Ccode>this\u003C\u002Fcode>对象的销毁和消失，简直完美。\u003C\u002Fp>\n\u003Cp>你可能会说，私有变量不是都已经有现成的方案了吗？\u003C\u002Fp>\n\u003Cpre>\u003Ccode>function Constructor() {\n    var data = {};\n\n \t\u002F\u002F 方法\n    this.doSth = function () {\n    \tvar privateVar = data;\n    \t......\n    };\n};\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>没错，这正是经典的私有变量解决方案。但是这个方案有一个问题，即所有访问私有变量的方法（如\u003Ccode>doSth()\u003C\u002Fcode>）都只能挂在实例上，即每个实例中除了私有变量外，还有自己的特权方法。而使用WeakMap实现私有变量的方案中，方法可以挂在原型上。这两者在性能上会有一些差异。\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>ES2015虽然有Class，但是仍然不支持私有变量的定义，要使用私有变量的话，实现方法和ES5没有太大差异。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2>WeakMap的模拟\u003C\u002Fh2>\n\u003Cp>如果你有关注过ES2015的特性在ES5中的模拟降级情况的话，应该会发现很多人说过，WeakMap是无法模拟的。事实的确是这样，但是如果我们稍微放宽一些条件，还是有办法模拟出一个可用的WeakMap的。\u003C\u002Fp>\n\u003Cp>首先，我们看一个\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fwebcomponents\u002Fwebcomponentsjs\">web components polyfill\u003C\u002Fa>中\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fwebcomponents\u002Fwebcomponentsjs\u002Fblob\u002Fmaster\u002Fsrc\u002FWeakMap\u002FWeakMap.js\">对WeakMap的模拟\u003C\u002Fa>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>if (typeof WeakMap === 'undefined') {\n  (function() {\n    var defineProperty = Object.defineProperty;\n    var counter = Date.now() % 1e9;\n\n    var WeakMap = function() {\n      \u002F\u002F 记录一个唯一的name属性\n      this.name = '__st' + (Math.random() * 1e9 &gt;&gt;&gt; 0) + (counter++ + '__');\n    };\n\n    WeakMap.prototype = {\n      \u002F\u002F 在WeakMap的key对象中添加与WeakMap实例name相同的属性\n      \u002F\u002F 利用这个属性来保存value\n      \u002F\u002F 下面的API原理类似\n      set: function(key, value) {\n        var entry = key[this.name];\n        if (entry &amp;&amp; entry[0] === key)\n          entry[1] = value;\n        else\n          defineProperty(key, this.name, {value: [key, value], writable: true});\n        return this;\n      },\n      get: function(key) {\n        var entry;\n        return (entry = key[this.name]) &amp;&amp; entry[0] === key ?\n            entry[1] : undefined;\n      },\n      delete: function(key) {\n        var entry = key[this.name];\n        if (!entry || entry[0] !== key) return false;\n        entry[0] = entry[1] = undefined;\n        return true;\n      },\n      has: function(key) {\n        var entry = key[this.name];\n        if (!entry) return false;\n        return entry[0] === key;\n      }\n    };\n\n    window.WeakMap = WeakMap;\n  })();\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>上面的代码中我们加了一点注释，这个模拟的核心在于，\u003Ccode>weakMap.set(key, value)\u003C\u002Fcode>时，将\u003Ccode>value\u003C\u002Fcode>直接写入\u003Ccode>key\u003C\u002Fcode>这个对象中，作为对象的一个属性。因此它的生命周期与对象本身完全一致，也不会影响对象的垃圾回收，基本达到WeakMap的核心诉求。\u003C\u002Fp>\n\u003Cp>而它也有一些不足之处，最大的问题就是会修改对象本身。我们前文说过，WeakMap的核心思想是“在不改变对象本身的情况下扩展对象”，而我们的模拟却刚好违背了这一思想，将值扩展到了对象本身。如上文所说，总有一些对象是不可修改的，在这种情况下就会出现问题。这也是刚刚提到的必须在放宽一些条件的情况下，才可以模拟WeakMap的意思。\u003C\u002Fp>\n\u003Cp>基于同样的原理，还有一个模拟WeakMap的库\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FBenvie\u002FWeakMap\">https:\u002F\u002Fgithub.com\u002FBenvie\u002FWeakMap\u003C\u002Fa>。这个库的源码可见这里\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FBenvie\u002FWeakMap\u002Fblob\u002Fmaster\u002Fweakmap.js\">https:\u002F\u002Fgithub.com\u002FBenvie\u002FWeakMap\u002Fblob\u002Fmaster\u002Fweakmap.js\u003C\u002Fa>，它将数据以及对数据的操作封装到了\u003Ccode>Data()\u003C\u002Fcode>中，然后将它挂到了对象的一个属性\u003Ccode>globalId\u003C\u002Fcode>上。详细的关系大概如下：\u003C\u002Fp>\n\u003Cpre class=\"shiki\" style=\"background-color:#121212;color:#dbd7caee\" tabindex=\"0\">\u003Ccode>object:{\n\t[globalId]: data{\n\t\t[puid1]: value1\n\t\t[puid2]: value2\n\t}\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>值得一提的是，\u003Ccode>globalId\u003C\u002Fcode>是全局唯一的，也就是说在一次脚本运行的过程中（不关掉页面，或者Node进程不退出），所有对象上的\u003Ccode>globalId\u003C\u002Fcode>都是相同的。\u003C\u002Fp>\n\u003Cp>相比web components的模拟而言，这个库的模拟要严谨细心得多，比如当对象不可写时会抛出错误，比如在产生随机ID时会进行重复判断，确保逻辑正确，比如它只在对象上写入\u003Ccode>[globalId]\u003C\u002Fcode>一个属性，尽量减少对对象的修改。当然这些逻辑也导致了代码可读性直线下降，解读这段代码花了我整整一个晚上的时间，有兴趣的同学可以自己看一下是否能够读懂。\u003C\u002Fp>\n\u003Ch2>结\u003C\u002Fh2>\n\u003Cp>本文简单总结了下自己在学习WeakMap过程中记录的相关知识点，包括WeakMap的特性、使用场景以及模拟实现。\u003C\u002Fp>\n\u003Cp>总体说来，WeakMap是一个有独特特性和使用场景的数据类型，它的出现使我们能更从容地应对一些开发过程中的问题。但话又说回来，在WeakMap出现之前，同样的问题我们也一样能解决，只是可能稍微麻烦一些，或者性能稍微差一些。\u003C\u002Fp>\n\u003Cp>另，本文所使用比喻仅为说明问题而设，各位看官明白要表达的意思即可，勿在比喻中钻牛角尖。\u003C\u002Fp>\n\u003Cp>参考资料：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fsanwen8.cn\u002Fp\u002F1e9XKBe.html\">https:\u002F\u002Fsanwen8.cn\u002Fp\u002F1e9XKBe.html\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fwww.sitepen.com\u002Fblog\u002F2015\u002F03\u002F19\u002Flegitimate-memory-efficient-privacy-with-es6-weakmaps\u002F\">https:\u002F\u002Fwww.sitepen.com\u002Fblog\u002F2015\u002F03\u002F19\u002Flegitimate-memory-efficient-privacy-with-es6-weakmaps\u002F\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"http:\u002F\u002Fwww.2ality.com\u002F2016\u002F01\u002Fprivate-data-classes.html\">http:\u002F\u002Fwww.2ality.com\u002F2016\u002F01\u002Fprivate-data-classes.html\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FWeakMap\">https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FWeakMap\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"http:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F29413222\u002Fwhat-are-the-actual-uses-of-es6-weakmap\">http:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F29413222\u002Fwhat-are-the-actual-uses-of-es6-weakmap\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"http:\u002F\u002Ffitzgeraldnick.com\u002F2014\u002F01\u002F13\u002Fhiding-implementation-details-with-e6-weakmaps.html\">http:\u002F\u002Ffitzgeraldnick.com\u002F2014\u002F01\u002F13\u002Fhiding-implementation-details-with-e6-weakmaps.html\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Filikekillnerds.com\u002F2015\u002F02\u002Fwhat-are-weakmaps-in-es6\u002F\">https:\u002F\u002Filikekillnerds.com\u002F2015\u002F02\u002Fwhat-are-weakmaps-in-es6\u002F\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fsegmentfault.com\u002Fa\u002F1190000002549235\">https:\u002F\u002Fsegmentfault.com\u002Fa\u002F1190000002549235\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FWeakMap\">https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FWeakMap\u003C\u002Fa>\u003C\u002Fli>\n\u003C\u002Ful>\n","\u003Cp>ES2015（ES6）中新增了几种数据类型，包括Map WeakMap Set WeakSet等。其中Map可以与我们熟悉的对象Object进行对照，他们的功能都是提供一个键值对集合，主要的区别在于Object的key只能是字符串，而Map的key可以是任意类型。关于Map的大致用法可以参考\u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FMap\">MDN\u003C\u002Fa>，我在前一篇文章\u003Ca href=\"https:\u002F\u002Fwww.toobug.net\u002Farticle\u002Farray_unique_in_javascript.html\">《也谈JavaScript数组去重》\u003C\u002Fa>中也有提及。\u003C\u002Fp>\n\u003Cp>今天要讨论的主角是WeakMap。\u003C\u002Fp>\n\u003Cp>按照\u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FWeakMap\">MDN\u003C\u002Fa>上的说明\u003C\u002Fp>\n\u003Cblockquote>\n\u003Cp>WeakMap 对象是键\u002F值对的集合，且其中的键是弱引用的。其键只能是对象，而值则可以是任意的。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>从这段描述来看，我们可以大致推断出，WeakMap与Map的主要区别在于两点：\u003C\u002Fp>\n\u003Col>\n\u003Cli>WeakMap对key的引用是弱引用\u003C\u002Fli>\n\u003Cli>WeakMap的key只能是对象\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>这两点意味着什么呢？反正我第一眼看到的时候不是拉格良日懵就是三元二次懵的状态。于是围绕WeakMap去查阅了一些资料，渐渐地有了一些更深入的认识，记录成本文。\u003C\u002Fp>\n","",{"total":19,"totalRoots":19,"comments":26,"pv":19},[]]