最近写一个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!


无风
670 声望63 粉丝