终于在 JS 中用上 WeakMap 了!

更新日期: 2021-11-12阅读: 1.1k标签: map

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


问题原因

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

// 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,我大吃一惊:

<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:

console.log(Object.keys(someObj));
// ['object HTMLSpanElement']

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

console.log(someObj[document.getElementById('el2')]);
// 'some value'

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

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

<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 呢。

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

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

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

var obj = new Object();

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

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

var obj = new WeakObject();

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

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

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

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


链接: https://fly63.com/article/detial/10791

JS 中为啥 [‘1‘, ‘7’,‘11’ ].map(parseInt) 返回 [1, NaN, 3]

Javascript 一直是神奇的语言。 不相信我? 尝试使用map和parseInt将字符串数组转换为整数。打开 Chrome 的控制台(F12),粘贴以下内容,然后按回车,查看输出结果:

为什么 [‘1’, ‘7’, ‘11’].map(parseInt) 的结果是 [1, NaN, 3]?

在 Javascript 中,一个函数可以传递任何多个数量的参数,即使调用时传递的数量与定义时的数量不一致。缺失的参数会以 undefined 作为实际值传递给函数体,然后多余的参数会直接被忽略掉

js中Map

在JavaScript中,Map 是存储键/值对的对象。Map 类似于一般 JavaScript 对象 ,但对象与 Map 之间一些关键的差异使 Map 很有用。如果你要创建一个存储一些键/值路径的 JavaScript 对象

typescript中继承Array、Map、Set报错的解决

Map、Set的polyfill实现是可以继承的;//可继承的Array替换原生Array,Array要改的地比较多,除了替换原生Array还需修改继承函数,供参考

用map代替纯JavaScript对象

普通的 JavaScript 对象通常可以很好地保存结构化数据。但是它们有一些限制:只能用字符串或符号用作键,自己的对象属性可能会与从原型继承的属性键冲突(例如,toString,constructor 等)。对象不能用作键

ES6的Map类型

Map的出现解决了传统object无法直接解决的问题,更好地向标准编程语言靠近(标准编程语言一般会提供Map集合),使用的坑也比较少(比如没有object作为key时转换为[object Object]的问题)。

es6 Set和Map数据结构

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

ES6 Map 原理

ES6的Map的键可以是任意的数据结构,并且不重复。那么map的底层原理是啥呢?Map利用链表,hash的思想来实现。首先,Map可以实现删除,而且删除的数据可以是中间的值。

js中forEach & map

JavaScript中,数组的遍历我们肯定都不陌生,最常见的两个便是forEach 和 map。(当然还有别的譬如for, for in, for of, reduce, filter, every, some, ...)

何时使用 Map 来代替普通的 JS 对象

JS 普通对象 {key: value} 用于存放结构化数据。但有一件事我觉得很烦:对象键必须是字符串(或很少使用的 symbol)。如果将数字用作键会怎样? 在这种情况下不会有错误:

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!