最近写一个web应用的图片上传功能,里面有这么个场景:点击上传按钮,呼出file input框,选择完图片进行前端压缩然后上传,完毕后将返回的图片链接展示给用户。这个功能很常见,但是在这里却翻了船,所以专门记录一下这个bug。
我是这么写的(vue代码片段):
methods: {
clickInput(item, e) {
this.uploadItem = e.target.parentNode;
this.item = item;
let input = this.input = this.uploadItem.querySelector('input');
async function changeHook() {
let compress = new CompressImg({
maxSize: 500 * 1024,
quality: 0.2,
files: this.input.files,
})
let blob = await compress.getBlob();
if (blob) {
this.uploadImg(blob);
}
}
input.removeEventListener('change', changeHook);
input.addEventListener('change', changeHook);
input.click();
},
}
此处写的很僵硬,直接在html中给input绑定事件即可,没必要动态绑定 捂脸
compressImg是参考网上大牛的方法用canvas的api进行前端压缩,具体见我的gist:https://gist.github.com/win5d...
为了防止绑定多个事件,这里先remove再add,看起来好像没毛病,看了下控制台,发现上传触发了多次,excuse me?
回头检查了一下代码,感觉没毛病啊,开始还以为是async函数惹的祸,改成普通函数发现还是有问题。静下来想了想发现我犯了一个低级错误。
上面的代码中changeHook函数是在clickInput这个函数(闭包)中申明的,在这个函数执行完毕后,由于它被绑上了事件,引用并不为null,所以没有被回收。下一次点击时又回创建一个新的changeHook函数,虽然都叫changeHook,但这两个函数在不同的闭包之中,占据不同的内存,调用removeEventListener也是然并卵,每次触发clickInput都会造成一次内存泄漏。
只需要把函数申明放到外面即可:
<button id="a">点击绑定事件</button>
<button id="b">点击触发事件</button>
<script>
let a = document.querySelector('#a');
let b = document.querySelector('#b');
a.addEventListener('click', () => {
b.removeEventListener('click', cb);
b.addEventListener('click', cb);
});
function cb() {
console.log('rua');
}
</script>
在函数里面申明函数,这个写法我也知道很坑爹,由于这个函数只在这个方法用到,而且强迫症不想在data里申明很多变量,偷懒不想处理参数传递,结果搞出这么个低级错误。强迫症得治,偷了的懒都得还,rua!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。