函数防抖与节流

防抖(Debounce)

立即执行版

通过上述防抖后的gif图我们可以看到,当用户连续输入内容时并不会返回结果,而是要满足我们的执行间隔时才会执行。

这样的效果如果有细(刁)心(钻)的产品的话,他应该不希望是这样,他会希望当用户输入时立即请求接口反馈结果,然后再等到输入间隔满足我们的间隔时间时再执行。

思路:

那就需要我们上面的防抖函数在调用时就执行一次。相比较刚才的防抖,立即执行版防抖只是多了一步立即执行。

代码实现:

/**

* @fn : 要执行的函数

* @delay : 执行执行函数的时间间隔

* @immediate : 是否立即执行函数 true 表立即执行,false 表非立即执行

*/

function debounce(fn,delay,immediate){

let timer; // 定时器

return function(...args){ // 形成闭包 外部执行的函数其实是这个return出去的函数。

// args 为函数调用时传的参数。

let context = this; // this 为函数执行时的this绑定。

timer&&clearTimeout(timer); // 当函数再次执行时,清除定时器,让定时器重新开始计时

// immediate为true 表示第一次触发就执行

if(immediate){

// 执行一次之后赋值为false

immediate = false;

fn.apply(context, args)

}

// 利用定时器,让指定函数延迟执行。

timer = setTimeout(function(){

// immediate 赋值为true 下次输入时 还是会立即执行

immediate = true;

// 执行传入的指定函数,利用apply更改传入函数内部的this绑定,传入 args参数

fn.apply(context,args);

},delay)

}

}

节流(Throttle)

实现思路

给定函数执行时间间隔为n,若 n 秒内没有函数再次执行,则执行该函数。若 n 秒内函数再次执行,则重新计算函数被执行的时间。

第一版:

利用setTimeout将传入的函数延迟执行,在延迟执行到达之前,如果函数又被执行,则清除定时器,让setTimeout重新计时。因此函数执行的条件为,在setTimeout计时结束前,传入的函数没有被再次执行,这时传入的函数就会执行。

/**

* @fn : 要执行的函数

* @delay : 执行执行函数的时间间隔

*/

function debounce(fn,delay){

let timer; // 定时器

return function(...args){ // 形成闭包

timer&&clearTimeout(timer); // 当函数再次执行时,清除定时器,让定时器重新开始计时

// 利用定时器,让指定函数延迟执行。

timer = setTimeout(function(){

// 执行传入的指定函数

fn();

},delay)

}

}

上述实现防抖函数并未实现传参和this绑定。

第二版:

/**

* @fn : 要执行的函数

* @delay : 执行执行函数的时间间隔

*/

function debounce(fn,delay){

let timer; // 定时器

return function(...args){ // 形成闭包 外部执行的函数其实是这个return出去的函数。

// args 为函数调用时传的参数。

let context = this; // this 为函数执行时的this绑定。

timer&&clearTimeout(timer); // 当函数再次执行时,清除定时器,让定时器重新开始计时

// 利用定时器,让指定函数延迟执行。

timer = setTimeout(function(){

// 执行传入的指定函数,利用apply更改this绑定和传参

fn.apply(context,args);

},delay)

}

}

使用

// 输入框

<input type="text" id="input"/>

let ipt = document.getElementById('input');

let handler = debounce(handleSendPhone,500);

//handler:debounce执行后return的函数。

ipt.addEventListener('input',function(){

let val = this.value;

handler(val);

});

// 请求接口

function handleSendPhone(val){

ajaxRequest({

user:val

}).then(res => {

console.log(res)

})

}

升级版

思路

用现在的时间减去上次执行函数时的时间如果大于规定的时间间隔,就可以认为可以执行当前函数。

即:currTime - prevTime > delay 。

/**

* @fn : 要执行的函数

* @delay : 每次执行函数的时间间隔

*/

function throttle(fn,delay){

let timer;

let prevTime; // 记录上一次执行的时间

return function(...args){

let currTime = Date.now(); // 获取当前时间时间戳

let context = this;

if(!prevTime) prevTime = currTime; // 第一次执行时prevTime赋值为当前时间

timer&&clearTimeout(timer); // 每次都清除定时器,保证定时器只是在最后一次执行

if(currTime - prevTime > delay){ // 如果为true ,则表示两次执行函数的时间间隔为delay.

prevTime = currTime;

fn.apply(context,args);

clearTimeout(timer); // 清除定时器。用来处理假如函数停止调用时刚好函数也停止执行,不需要获取后续的值。 详见下面定时器的介绍。

return;

}

// 当上面执行currTime - prevTime > delay 为false时,执行定时器。

// 用来处理:假如下次函数执行时间未到,函数不继续调用了,会造成最后一次函数执行 到 最后一次函数调用之间的值获取不到。

timer = setTimeout(function(){

prevTime = Date.now();

timer = null;

fn.apply(context,args);

},delay);

}

}