当我在处理一个滑动组件时,遇到了一个问题,当我快速切换元素的打开和关闭状态时,如果不允许上一个动画完成,新动画最终会失控,阻断后面的动画效果。
因为每次触发动画时,我都会获取元素的当前“原始”高度,无论它是不是在渲染动画,这个库使用的是 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
使用,所以这个方法也不是那么好。
这时,有一个朋友给我贴了段代码,使用的是 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
终于派上用场了~
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8