[TOC]

107.JavaScript防抖和节流

1.函数防抖

1.1 概念

在某时间段内多次触发相同事件,仅执行一次触发的事件.
防抖仅执行一次!

1.2 应用场景

1.浏览器窗口的缩放 resize 事件

7.JavaScript防抖和节流 - 图1

2.浏览器滚动条 scroll 事件

7.JavaScript防抖和节流 - 图2

3.防抖用来解决某时间段内高频率触发相同事件而做的性能优化,所以没有什么特定的场景完全根据业务需求而定.

1.3 实现

以浏览器窗口缩放 resize 事件为场景梳理一个清单,看大概需要几个步骤:

  1. 实现事件处理函数.
  2. 使用 setTimeout 创建定时器,到时间触发处理函数.
  3. 每次创建定时器之前,首先取消上次定时器.

    1.实现事件处理函数.







    改变浏览器窗口的大小触发resize事件.



    2.使用 setTimeout 创建定时器,到时间触发处理函数.







    改变浏览器窗口的大小触发resize事件.




    如果执行过上面的代码效果应该和下面的动图差不多,对比——匿名函数—-的输出频率会发现使用定时器执行的函数输出触发缩放浏览器窗口resize事件.的频率降低了很多…
    7.JavaScript防抖和节流 - 图3

    3.每次创建定时器之前,首先取消上次定时器.







改变浏览器窗口的大小触发resize事件.



#### 完善 应该补充完善以下几点: - 配置首次触发立即执行函数或最后一次触发事件后执行函数 - 参数处理 - 返回值





改变浏览器窗口的大小触发resize事件.



1.4.debounce的underscore 防抖

源码基于 v1.10.2 版本,直接在源码上添加理解注释.
/*

underscore. debounce 函数理解
@param {Function } 事件处理函数
@param {Number} wait 触发的时间
@param {Boolean} immediate 是否立即执行事件处理函数
@returns Function 可执行函数
/
function debounce(func, wait, immediate) {
//timeout记录定时器是否已创建
//result记录func函数返回值(事件处理函数有可能会存在返回值的情况)
var timeout, result;
var later = function (context, args) {
timeout = null;
if (args) result = func.apply(context, args);
};
// 了解restArguments函数之前先看一下ES6的rest参数语法,参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Rest_parameters
// restArguments是underscore提供的用来实现类似ES6 的rest参数语法的一个函数,此函数返回一个可执行的function
// 这里调用restArguments函数只是用来对参数做处理(非重点),或者可以把它当作一个匿名函数即可.
// var debounced=function(){}
var debounced = restArguments(function (args) {
//如果定时器存在则取消(这样无论触发多少次定时器,都会把之前的先取消下面重新开始设置定时器,以确保只触发一次定时器).
if (timeout) clearTimeout(timeout);
//按照我们上面介绍防抖概念时讲到防抖仅执行一次,配合挤公交、地铁场景会发现仅’执行的这一次’一般情况下是最后一次触发事件时执行,但确实存在首次触发事件即执行的业务场景.所以immediate参数是用来判断以下两种情况
//true:1.第一次触发事件立即执行事件处理函数;
//false:2.最后一次触发事件执行事件处理函数;
//无论哪种情况还是那句话”防抖仅执行一次!”
//我们直接看if内如何做到第一次触发事件立即执行事件处理函数,后面的全部取消的.
if (immediate) {
//既然进入if说明immediate=true需要立即执行
//首先获取定时器是否存在
var callNow = !timeout;
//划重点-划重点-划重点
//其次创建定时器,等待到时间触发事件处理函数,注意这里虽然触发了定时器,但later函数内if(args)永远为false,简单而言就是触发了定时器,定时器内的函数不会触发,因为通过setTimeout(later, wait)这种形式触发later函数是无法传递参数的,所以later函数内(context, args)皆为undefined;这样配合下面的if(callNow)便做到了首次触发之后,后面的全部取消(不执行).
timeout = setTimeout(later, wait);
//if(定时器不存在),不需要通过setTimeout设置定时器而是立即执行func函数,这样就做到了首次触发事件立即执行
if (callNow) result = func.apply(this, args);
} else {
//delay作为underscore的工具函数,主要做了2件事情
//1.调用restArguments对参数做处理
//2.返回setTimeout
//或者直观理解为timeout=setTimeout(later,wait);只不过这样无法给later传参数,所以这里delay函数内部把this、args参数通过restArguments传递later函数
timeout = delay(later, wait, this, args);
}
//关于这个返回值啰嗦一下,如果immediate=true进入if会获取到result返回值;相反immediate=false,result一直为undefined
//为什么会出现这种情况,由于进入if(immediate)会立即执行函数result = func.apply(this, args),所以会获取到返回值;而else内由于delay函数其实是一个setTimeout定时器,由于代码异步执行,还未等到触发时间,代码已经执行了return result;所以出现了result=undefined的情况.
return result;
});
//添加静态方法,取消定时器
debounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
把上面的理解总结大概以下几点:

实现事件处理函数(debounced)

实现立即执行、最后一次执行(immediate)

处理参数

处理返回值

实现取消定时器(debounced.cancel)

1.5. lodash的 debounce函数

/

lodash. debounce 函数理解
@param {Function } 事件处理函数
@param {Number} wait 触发的时间
@param {Object} options 选项配置
@returns Function 可执行函数
*/
function debounce(func, wait, options) {
var lastArgs,
lastThis,
maxWait, //最大等待时间(间隔时间),这个参数其实是作为节流使用的
result,
timerId,
lastCallTime, //最后一次触发事件的时间(毫秒数)
lastInvokeTime = 0, //最后一次执行事件处理函数的时间(毫秒数)
leading = false, //首次触发事件则立即执行事件处理函数
maxing = false, // 是否开启,根据该参数其实就是判断了是否开始节流模式
trailing = true; //触发事件结束后,等到时间执行事件处理函数
if (typeof func != ‘function’) {
throw new TypeError(FUNC_ERROR_TEXT);
}
wait = toNumber(wait) || 0; //等待时间
if (isObject(options)) {
leading = !!options.leading; //转换为Boolean类型
maxing = ‘maxWait’ in options; //如果配置了最大等待时间说明要实现节流的效果,根据最大间隔时间来间断的触发事件
maxWait = maxing
? nativeMax(toNumber(options.maxWait) || 0, wait)
: maxWait; //设置最大间隔时间
trailing = ‘trailing’ in options ? !!options.trailing : trailing;
}
/


立即执行(调用)事件处理函数(func)
@param {Number} time 调用时间(毫秒数)
@returns
/
function invokeFunc(time) {
var args = lastArgs,
thisArg = lastThis;
lastArgs = lastThis = undefined; //重置
lastInvokeTime = time; //重置最后一次执行事件处理函数的时间=此次时间(毫秒数)
result = func.apply(thisArg, args); //执行func
return result;
}
/**
划重点-划重点-划重点
从函数名来此函数只负责判断首次触发事件是否需要调用invokeFunc函数(立即执行事件处理函数)
但它还创建了一个定时器,为什么要做两件事情(1.判断是否立即执行事件函数;2.创建定时器)???
做第一件事情是它的本分应该做的,第二件事情创建定时器是为了后续的操作铺路
别忘了防抖函数debounce第三个参数是可以配置{leading:true,trailing:true}两个参数都为true的情况存在
即leading:true首次触发事件立即执行事件处理函数,同时trailing:true触发事件结束后执行事件处理函数
所以这里创建定时器是为后面的trailing:true的情况做准备.
为什么会出现允许都为true的情况,可以看文章结尾问答部分第一问题
@param {Number} time 调用时间(毫秒数)
@returns
/
function leadingEdge(time) {
lastInvokeTime = time; //重置最后一次执行事件处理函数的时间=此次时间(毫秒数)
timerId = setTimeout(timerExpired, wait); //创建定时器
//根据leading选项判断是否需要调用invokeFunc
return leading ? invokeFunc(time) : result;
}
/

计算触发setTimout的时间
@param {} time
@returns
/
function remainingWait(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime,
timeWaiting = wait - timeSinceLastCall;
return maxing
? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting;
}
/


根据时间计算时差是否应该发起调用
@param {Number} time
@returns Boolean
/
function shouldInvoke(time) {
//两次防抖时间差=本次触发防抖的时间-上次(最后一次)触发防抖的时间
//(节流模式用到)事件处理函数时间差=本次触发防抖的时间-上次(最后一次)执行事件处理函数的时间
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime;
//以下对各种情况做判断,只要有一项返回true则发起调用
//1. lastCallTime === undefined 如果值为undefined说明是首次触发(因为lastCallTime初始化为undefined)
//2. timeSinceLastCall >= wait 两次防抖时间差大于设置的防抖函数等待时间,说明此次防抖与上次防抖不是同一批次防抖,而是又一次重新开始(啰嗦的比较绕),如下面的示例
// 如定时器需要5秒后执行,在5秒期间不断触发debounced事件的时候,会计算这期间每次触发防抖事件的时间差,
//如果时间差小于设置的等待时间5秒,则说明是在5秒内发生的事件返回false,相反如果大于5秒说明不是在1-5秒内发生的事件则返回ture
//3. timeSinceLastCall < 0 时间难道不是下一秒永远大于上一秒吗?如果手动调整了系统时间穿越回去某一天了,那时差是不是就小于0了(过去某时间-现在某时间<0)
//4. maxing && timeSinceLastInvoke >= maxWait maxing为true说明是设置了间隔时间(开启了节流模式),
// timeSinceLastInvoke >= maxWait 判断事件处理函数的时间差是否大于设置的最大间隔事件.
return (
lastCallTime === undefined ||
timeSinceLastCall >= wait ||
timeSinceLastCall < 0 ||
(maxing && timeSinceLastInvoke >= maxWait)
);
}
/**

定时器到时间之后需要执行的函数 setTimeout(timerExpired, wait)
不断的重新计算剩余时间
@returns
/
function timerExpired() {
var time = now();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// 重新计算时间
timerId = setTimeout(timerExpired, remainingWait(time));
}
/

判断触发事件结束后是否需要调用invokeFunc函数(立即执行事件处理函数)
默认trailing:true 采用触发事件结束后执行事件处理函数
@param {Number} time 调用时间(毫秒数)
@returns
/
function trailingEdge(time) {
timerId = undefined;
//根据trailing选项、lastArgs 判断是否需要调用invokeFunc
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
/


取消定时器
/
function cancel() {
if (timerId !== undefined) {
clearTimeout(timerId);
}
lastInvokeTime = 0;
lastArgs = lastCallTime = lastThis = timerId = undefined;
}
/**

立即触发处理函数
@returns
/
function flush() {
return timerId === undefined ? result : trailingEdge(now());
}
/**

返回一个可执行函数
var debounced=function(){}
@returns
/
function debounced() {
var time = now(),
isInvoking = shouldInvoke(time);
lastArgs = arguments;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
//maxing设置间隔时间(节流模式)
if (maxing) {
clearTimeout(timerId);
timerId = setTimeout(timerExpired, wait);
return invokeFunc(lastCallTime);
}
}
if (timerId === undefined) {
timerId = setTimeout(timerExpired, wait);
}
return result;
}
debounced.cancel = cancel;
debounced.flush = flush;
return debounced;
}

(1)lodash 版本的 debounce :

可以通过参数配置首次触发事件执行事件处理函数+最后一次触发事件后执行事件处理函数。
重要的事情再说一遍:记住:防抖仅执行一次!
无论是首次触发事件执行事件处理函数还是最后一次触发事件后执行事件处理函数,只是执行时间不同而已。
不信你看 underscore 版本的防抖函数debounce第三个参数immediate只是用来判断实现立即执行或最后一次执行,不允许出现执行 2 次的情况存在.
再回到 lodash 版本的debounce,如果仔细看会发现debounce第三个参数的默认配置{leading:false,trailing :true},也就是说认可防抖仅执行一次的,之所以允许配置{leading:true,trailing :true}的情况存在是由于 lodash 的throttle节流函数的实现是通过调用debounce函数完成的,所以可以理解为{leading:true,trailing :true,maxWait:1000}此处的 2 个true和maxWait参数都是为节流函数提供的.
所以要使用 lodash 的防抖或节流需要配合业务场景,尽量少出现使用防抖的时候配置{leading:false,trailing :true}这种场景混用情况的存在.(此场景属于每间隔一段时间执行一次事件处理函数应该使用节流函数)

(2)使用方法

1.lodash的debounce函数的使用

最新,在react新项目的开发中使用到了lodash类库的debounce方法,就随手梳理了一下此方法的方便之处
7.JavaScript防抖和节流 - 图4

未使用debounce之前

如果不考虑使用debounce,那么在用户连续点击的情况之下,会在每一次点击之后就会执行自定义函数的回调,这时如果你的回调中处理一些比较消耗内存的一些操作,或者调用接口之类,那么可能会导致阻塞甚至于项目崩溃。
例如:
window.addEventListener(‘click’, function (event) {
var p = document.createElement(‘p’)
p.innerHTML = ‘trigger’
document.body.appendChild(p)
})
此时的每一次点击都会触发dom元素的改变,如果连续点击的情况下后果可想而知

引入debounce

window.addEventListener(‘click’, debounce(function (event) {
var p = document.createElement(‘p’)
p.innerHTML = ‘trigger’
document.body.appendChild(p)
return ‘aaaa’
}, 500))
这样,即使用户连续点击,那么也只有在最后一次点击的500ms后,真正的函数func才会触发。
这只是对debounce的初步了解,后续更新中

(3)使用 debounce 函数实现防抖

1,安装配置

(1)这里假设我们需要在 Vue.js 项目里使用 Lodash,首先进入项目文件夹,执行如下命令使用 npm 安装 lodash

1 npm i —save lodash

(2)然后项目代码中引入使用即可:

1 import _ from ‘lodash’

2,debounce 防抖

debounce 函数原型如下。它会创建一个 debounced(防抖动)函数,该函数会从上一次被调用后,延迟 wait 毫秒后调用 func 方法。

1 _.debounce(func, [wait=0], [options={}])

(1)debounce 常用来过滤高频产生的方法调用:

  • 比如我们要实现一个支持实时搜索的输入框,为了避免每输入一个字符就发送一个请求而造成资源浪费。我们可以借助 debounce来实现防抖,当用户连续输入文字时不发送请求,只有输入完毕后等待一定时间不再输入时才发送请求。

(2)options 有如下三个可选参数配置:

  • leading:指定在延迟开始前是否执行 func(默认为 false)。
  • maxWait:设置 func 允许被延迟的最大值。
  • trailing:指定在延迟结束后是否执行 func(默认为 true)。

3,使用样例

(1)效果图

  • 点击按钮界面会弹出一个消息框。
  • 但如果我们连续点击按钮(间隔小于 500 毫秒)时,消息框不会弹出。只有停下后才会消息框(相当于只执行最后一个点击行为)

7.JavaScript防抖和节流 - 图5
(2)样例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32


(3)上面代码也可改成如下形式,效果是一样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27


附:debounce 其它的一些应用场景

Lodash 的官方在线手册(点击跳转)中还列举了一些 debounce 函数的使用场景,具体如debouncesourcenpm

_.debounce(func, [wait=0], [options])
创建一个防抖动函数。 该函数会在 wait 毫秒后调用 func 方法。 该函数提供一个 cancel 方法取消延迟的函数调用以及 flush 方法立即调用。 可以提供一个 options 对象决定如何调用 func 方法, options.leading 与|或 options.trailing 决定延迟前后如何触发。 func 会传入最后一次传入的参数给防抖动函数。 随后调用的防抖动函数返回是最后一次 func 调用的结果。

注意: 如果 leading 和 trailing 都设定为 true。 则 func 允许 trailing 方式调用的条件为: 在 wait 期间多次调用防抖方法。

查看 David Corbacho’s article 了解 .debounce 与 .throttle 的区别。

参数

  1. func (Function)

要防抖动的函数

  1. [wait=0] (number)

需要延迟的毫秒数

  1. [options] (Object)

选项对象

  1. [options.leading=false] (boolean)

指定调用在延迟开始前

  1. [options.maxWait] (number)

设置 func 允许被延迟的最大值

  1. [options.trailing=true] (boolean)

指定调用在延迟结束后

返回值 (Function)

返回具有防抖动功能的函数

示例

// 避免窗口在变动时出现昂贵的计算开销。
jQuery(window).on(‘resize’, _.debounce(calculateLayout, 150));

// 当点击时 sendMail 随后就被调用。
jQuery(element).on(‘click’, _.debounce(sendMail, 300, {
‘leading’: true,
‘trailing’: false
}));

// 确保 batchLog 调用1次之后,1秒内会被触发。
var debounced = _.debounce(batchLog, 250, { ‘maxWait’: 1000 });
var source = new EventSource(‘/stream’);
jQuery(source).on(‘message’, debounced);

// 取消一个 trailing 的防抖动调用
jQuery(window).on(‘popstate’, debounced.cancel);

1.6.underscore、 lodash 的防抖有什么不同之处.

underscore lodash
实现方式 更直接的循环使用触发事件->取消定时器->设置定时器 通过计算时间差的方式设置定时器;
函数划分 把防抖、节流分为 2 个独立的函数各司其职 lodash 的节流函数内部则是调用了防抖函数,通过配置项进行区分.

2.函数节流

2.1概念

在某时间段内多次触发相同事件,则每间隔一段时间执行一次事件.
**节流每间隔一段时间执行一次!**

2.2 应用场景

1.生活场景

看一下节假日的高速、景区场景…
每逢节假日高速、景区都会开始节流模式,如高速收费站每间隔 10 分钟通过 4 辆车,景区每间隔 20 分钟入园 30 人….

2.在开发中搜索框的实时检索的场景是不是可以使用节流实现呢?

3.节流是用来解决某时间段内高频率触发相同事件而做的性能优化,所以没有什么特定的场景完全根据业务需求而定.

2.3 实现

我们以 div 容器的 mousemove 事件为场景梳理一个清单,看大概需要几个步骤:

1.实现事件处理函数.

2.使用 setTimeout 创建定时器,到时间触发处理函数.








0



完善

  • 配置首次触发立即执行函数或最后一次触发事件后执行函数
  • 参数处理
  • 返回值








0



2.4 Underscore的节流throttle

源码基于 v1.10.2 版本,直接在源码上添加理解注释.
/*

underscore. throttle 函数理解
@param {Function } 事件处理函数
@param {Number} wait 触发的时间
@param {Object} options 选项配置{leading:true,trailing:true}
leading:true(默认),首次触发事件则立即执行事件处理函数
leading:false 首次触发事件则不立即执行事件处理函数
trailing:true(默认),触发事件结束后再执行一次事件处理函数
trailing:false 触发事件结束后不执行事件处理函数
@returns Function 可执行函数
/
function throttle(func, wait, options) {
//timeout记录定时器状态
//context上下文
//args传递参数
//result记录func函数返回值(事件处理函数有可能会存在返回值的情况)
var timeout, context, args, result;
var previous = 0; //上次触发节流时间
if (!options) options = {}; //选项配置
var later = function () {
previous = options.leading === false ? 0 : now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function () {
var _now = now();
if (!previous && options.leading === false) previous = _now;
//判断触发节流事件的时间差
//剩余时间=等待执行事件的时间-(当前触发节流事件的时间-上次触发节流事件的时间)
var remaining = wait - (_now - previous);
context = this;
args = arguments;
//时间差<=0 说明此次触发节流事件的时间与上次触发节流事件的时间间隔大于设置的等待时间,可以执行配置的事件处理函数
//时间差>等待时间 说明手动设置了系统时间,如当前是 2020-08-17 10:27 38 ,下一秒 修改了系统时间为 2020-08-17 10:25 38,
//最后计算出来的时差则是 remaining > wait
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = _now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
//添加静态方法,取消定时器
throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
}

2.5 lodash的throttle

源码基于 v4.17.15 版本,直接在源码上添加理解注释.
/*

lodash. throttle 函数理解
@param {Function } 事件处理函数
@param {Number} wait 触发的时间
@param {Object} options 选项配置{leading:true,trailing:true,maxWait:wait}
leading:true(默认),首次触发事件则立即执行事件处理函数
leading:false 首次触发事件则不立即执行事件处理函数
trailing:true(默认),触发事件结束后再执行一次事件处理函数
trailing:false 触发事件结束后不执行事件处理函数
maxWait:最大等待时间(间隔时间)
@returns Function 可执行函数
*/
function throttle(func, wait, options) {
var leading = true,
trailing = true;
if (typeof func != ‘function’) {
throw new TypeError(FUNC_ERROR_TEXT);
}
if (isObject(options)) {
leading = ‘leading’ in options ? !!options.leading : leading;
trailing = ‘trailing’ in options ? !!options.trailing : trailing;
}
return debounce(func, wait, {
leading: leading,
maxWait: wait,
trailing: trailing,
});
}

3.函数节流

方法1:setInterval方法

方法2:Underscore的debounce()和throttle()方法

函数节流是指控制一个函数的执行频率或间隔(就像控制水流的闸门一样),Underscore提供了debounce()和throttle()两个方法用于函数节流。
为了更清楚地描述这两个方法,假设我们需要实现两个需求:

需求1:当用户在文本框输入搜索条件时,自动查询匹配的关键字并提示给用户(就像在Tmall输入搜索关键字时那样)

首先分析第1个需求,我们可以绑定文本框的keypress事件,当输入框内容发生变化时,查询匹配关键字并展示。假设我想查询“windows phone”,它包含13个字符,而我输入完成只花了1秒钟,那么在这1秒内,调用了13次查询方法。这是一件非常恐怖的事情,如果Tmall也这样实现,我担心它会不会在光棍节到来之前就挂掉了(当然,它并没有这么脆弱,但这绝对不是最好的方案)
更好的方法是,我们希望用户已经输入完成,或者正在等待提示(也许他懒得再输入后面的内容)的时候,再查询匹配关键字。
最后我们发现,在我们期望的这两种情况下,用户会暂时停止输入,于是我们决定在用户暂停输入200毫秒后再进行查询(如果用户在不断地输入内容,那么我们认为他可能很明确自己想要的关键字,所以等一等再提示他)
这时,利用Underscore中的debounce()函数,我们可以轻松实现这个需求:
html 代码:


你能看到,我们的代码非常简洁,节流控制在debounce()方法中已经被实现,我们只告诉它当query函数在200毫秒内没有被调用过的话,就执行我们的查询操作,然后再将query函数绑定到输入框的keypress事件。
query函数是怎么来的?我们在调用debounce()方法时,会传递一个执行查询操作的函数和一个时间(毫秒数),debounce()方法会根据我们传递的时间对函数进行节流控制,并返回一个新的函数(即query函数),我们可以放心大胆地调用query函数,而debounce()方法会按要求帮我们做好控制。

需求2:当用户拖动浏览器滚动条时,调用服务器接口检查是否有新的内容

再来分析第2个需求,我们可以将查询方法绑定到window.onscroll事件,但这显然不是一个好的做法,因为用户拖动一次滚动条可能会触发几十次甚至上百次onscroll事件。
我们是否可以使用上面的debounce()方法来进行节流控制?当用户拖动滚动条完毕后,再查询新的内容?但这与需求不符,用户希望在拖动的过程中也能看到新内容的变化。
因此我们决定这样做:用户在拖动时,每两次查询的间隔不少于500毫秒,如果用户拖动了1秒钟,这可能会触发200次onscroll事件,但我们最多只进行2次查询。
利用Underscore中的throttle()方法,我们也可以轻松实现这个需求:
html 代码:

代码仍然十分简洁,因为在throttle()方法内部,已经为我们实现的所有控制。

debounce()和throttle()对比:

你可能已经发现,debounce()和throttle()两个方法非常相似(包括调用方式和返回值),作用却又有不同。
它们都是用于函数节流,控制函数不被频繁地调用,节省客户端及服务器资源。

  • debounce()方法关注函数执行的间隔,即函数两次的调用时间不能小于指定时间。
  • throttle()方法更关注函数的执行频率,即在指定频率内函数只会被调用一次。