关于防抖节流里面的this指向问题

例如

//防抖(非立即执行)
function debounce(fn, delay = 500) {
    let timer = null;
    return function () {
        if (timer) {
            clearTimeout(timer);
            timer = null
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments);
            timer = null;
        }, delay)
    }
}

这个fn绑定this执行的意义何在? 求解

阅读 9.1k
2 个回答

因为如果不绑定 this,会出现典型的 this 丢失的情况。
从这个 debounce 的代码看,它的功能是通过传入一个 fn,得到这个 fn 的延迟执行版本,方便后续调用。
所以,可以假设一下用例:

let test = { name: 'testThis' };

test.debounceFn = debounce(function () {
    console.log("for test: ", this.name);
});

test.debounceFn();

如果用 apply 绑定了 this,运行结果是: for test: testThis
如果未绑定,运行结果是:for test: undefined
这是因为 test.debounceFn() 的 this 指向是这个 test 对象。但是 debounceFn 方法中的 fn 方法,作为参数传入 setTimeout 后,是独立运行的,this 指向丢失。
这里是通过 test.debounceFn -> 箭头函数 -> fn.apply 一步步把 test 对象作为 this 值绑定给了 fn。

假如你在使用 Vue,其中的一个 method 需要防抖,那么最简洁的写法是这样:

{
    methods: {
        click: debounce(function(){
            // ↑上面的 function 就是防抖函数里的 fn,但是 fn 实际上
            // 是由防抖函数里的 function 调用的,调用的时候如果不给
            // 它绑定 this,下面这句代码就不能用 this 访问 Vue 实例
            this.counter ++;
        })
    }
}

上面的例子是 debounce 提供了 this 绑定的前提,可以看出来,在写法上仅仅比常规的用法多了一个 debounce 的调用,非常直观。

而如果 debounce 函数不提供 this 的绑定,同样的功能可能得这样写:


{
    methods: {
        // 为了夸大对比效果我特意用了 IIFE,实际上可以把 handler 
        // 放到外面,以避免使用 IIFE
        click: (() => {
            let args = null;
            // 这里的 handler 只是一个用来调用函数的“中介”
            // 它负责把函数传递给防抖函数,由防抖函数调用
            const handler = debounce(fn => fn(...args));
            return function(...realArgs){
            
                // 把真正的参数缓存起来,
                args = realArgs;
                
                handler(() => {
                    // 这里对应上面代码里的 `fn` ,也就是说,这里才是真正的
                    // 业务逻辑,箭头函数的特性保证了这里的 this 是指向 Vue
                    // 实例的,如果不用箭头函数,还需要自己绑定一次 this
                    this.counter++;
                });
            }
        })()
    }
}
这个反例异常复杂,连我自己都有点晕乎。
推荐问题