前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >终于在 JS 中用上 WeakMap 了!

终于在 JS 中用上 WeakMap 了!

作者头像
ConardLi
发布2021-10-19 13:13:43
8240
发布2021-10-19 13:13:43
举报
文章被收录于专栏:code秘密花园code秘密花园

当我在处理一个滑动组件时,遇到了一个问题,当我快速切换元素的打开和关闭状态时,如果不允许上一个动画完成,新动画最终会失控,阻断后面的动画效果。

问题原因

因为每次触发动画时,我都会获取元素的当前“原始”高度,无论它是不是在渲染动画,这个库使用的是 Web Animations API,参考下面的代码:

代码语言:javascript
复制
// For each trigger, animate between zero and the `clientHeight` of the element.
let frames = ["0px", `${element.clientHeight}px`].map((height) => {
  return { height, overflow: "hidden" };
});

为了解决这个问题,我需要在滑动组件第一次使用时计算并缓存一次展开的高度,然后在每次触发动画时引用这个缓存。这样,每个页面加载时都会有一个固定的扩展高度值来进行动画的移动,并且不会再因为快速点击而引起这样怪异的现象。

几个选择

很快我想到了几个可能的解决方案。

首先,将这个值存储在目标元素的属性中:这本来是可以实现的,但是不太优雅,当我们审查页面元素时,不希望看到一堆乱七八糟的属性,特别是其他的库可能也需要他们自己的属性,累加起来这些标签的属性可能会变得非常负载,于是我选择弃用这个方法。

另外就是在 window 增加一个缓存对象。但是一个页面上可能同时有多个滑动组件。所以一个单独的 window.seCache 变量不能满足我们的需求。我们需要的是拥有某种键值对的对象。我可以在其中存储对每个元素的引用和相应的扩展高度值。

但它有一个 key 的限制:普通的对象是不允许使用 HTML 节点作为属性的,因此我还需要要求每个元素上都存在一个唯一标识符,作为 key 使用,所以这个方法也不是那么好。

使用 DOM 节点作为 key

这时,有一个朋友给我贴了段代码,使用的是 ES6 的 Computed property names,我大吃一惊:

代码语言:javascript
复制
<span id="el1">first element</span>
<span id="el2">second element</span>

<script>
  const someObj = {
    [document.getElementById('el1')]: 'some value'
  };

  console.log(someObj[document.getElementById('el1')]);
  // 'some value'
</script>

确实,通过 DOM 访问这个值确实会返回所需的值。但是,在深入研究之后,我意识到它并不是根据对该对象的引用执行查找的。相反,它是将其转换为该对象的字符串表示形式,然后将其用作 key:

代码语言:javascript
复制
console.log(Object.keys(someObj));
// ['object HTMLSpanElement']

所以以下任何一项也将访问到相同的值:

代码语言:javascript
复制
console.log(someObj[document.getElementById('el2')]);
// 'some value'

console.log(someObj[document.createElement('span')]);
// 'some value'

这时另一种选择就来了:一组新的原生 JavaScript 对象,允许你使用对象作为键 —— 包括对 DOM 节点本身的引用。也就是 MapWeakMap 对象。例如:

代码语言:javascript
复制
<span id="thing" class="thing">a thing.</span>

<script>
const myWeakMap = new WeakMap();

// Set a value to a specific node reference.
myWeakMap.set(document.getElementById('thing'), 'some value');

// Access that value by passing the same reference.
console.log(myWeakMap.get(document.querySelector('.thing')); // 'some value'
</script>

标准的 Map 是可以解决问题的,但是为啥在这里使用 WeakMap 呢。

WeakMapMap 的主要区别就是:WeakMap 的键名所引用的对象是弱引用。

弱引用:在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。

JavaScript 中,一般我们创建一个对象,都是建立一个强引用:

代码语言:javascript
复制
var obj = new Object();

只有当我们手动设置 obj = null 的时候,才有可能回收 obj 所引用的对象。

而如果我们能创建一个弱引用的对象:

代码语言:javascript
复制
var obj = new WeakObject();

我们什么都不用做,只用静静的等待垃圾回收机制执行,obj 所引用的对象就会被回收。

所以,现在这个场景我们使用 WeakMap 再合适不过了, WeakMap 使用的所有的 key 都会在合适的场景下被回收,我们就不用担心内存泄漏了~

下面再来看看我们的代码:

代码语言:javascript
复制
window.seCache = window.seCache || WeakMap.new();

function getExpandedHeight() {
  // We already have the calculated height.
  if(window.seCache.get(element)) {
    return window.seCache.get(element);
  }

  // This is the first run. Calculate & cache the full height.
  element.style.display = "block";
  window.seCache.set(element, element.clientHeight);
  element.style.display = "none";

  return window.seCache.get(element);
}

// For each trigger, animate between zero and the `clientHeight` of the element.
let frames = ["0px", `${getExpandedHeight()}px`].map((height) => {
  return { height, overflow: "hidden" };
});

至此,曾经只在面试题里出现的 WeakMap 终于派上用场了~

本文译自:https://macarthur.me/posts/when-a-weakmap-came-in-handy

抖音前端正急缺人才,如果你想加入我们,欢迎加我微信和我联系。另外如果你想加入前端、面试、理财等交流群,或者你有任何其他事情想和我交流也可以添加我的个人微信 ConardLi 。

文中如有错误,欢迎在后台和我留言,如果这篇文章帮助到了你,欢迎点赞、在看和关注。你的点赞、在看和关注是对我最大的支持!

点赞、在看是最作者最大的支持 ❤️

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-10-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 code秘密花园 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题原因
  • 几个选择
  • 使用 DOM 节点作为 key
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档