有时候会在项目开发中频繁地触发一些事件,如 resize
、 scroll
、 keyup
、 keydown
等,或者诸如输入框的实时搜索功能,咱们知道若是事件处理函数无限制调用,会大大加剧浏览器的工做量,有可能致使页面卡顿影响体验;后台接口的频繁调用,不只会影响客户端体验,还会大大增长服务器的负担。而若是对这些调用函数增长一个限制,让其减小调用频率,岂不美哉?浏览器
针对这个问题,通常有两个方案: 防抖 (Debounce) 节流 (Throttle)服务器
我对函数防抖的定义:当函数被连续调用时,该函数并不执行,只有当其所有中止调用超过必定时间后才执行1次。app
一个被常常提起的例子:dom
上电梯的时候,你们陆陆续续进来,电梯的门不会关上,只有当一段时间都没有人上来,电梯才会关门。函数
Talk is cheap,咱们直接 show code 吧。工具
先作基本的准备(篇幅缘由,HTML部分省略):ui
let container = document.getElementById('container');
// 事件处理函数
function handle(e) {
console.log(Math.random());
}
// 添加滚动事件
container.addEventListener('scroll', handle);
复制代码
咱们发现,每滚动一下,控制台就会打印出一行随机数。this
咱们如今写一个最基础的防抖处理:spa
function debounce(func, wait) {
var timeout;//标记
return function() {
clearTimeout(timeout);
timeout = setTimeout(func, wait);
}
}
复制代码
事件也作以下改写:code
container.addEventListener('scroll', debounce(handle, 1000));
复制代码
如今试一下, 咱们会发现只有咱们中止滚动1秒钟的时候,控制台才会打印出一行随机数。
以上基础版本会有两个问题,请看以下代码:
// 处理函数
function handle(e) {
console.log(this); //输出Window对象
console.log(e); //undefined
}
复制代码
没错,当咱们不使用防抖处理时,handle()
函数的this
指向调用此函数的container,而在外层使用防抖处理后,this的指向会变成Window。 其次,咱们也要获取到事件对象event
。
因此咱们要对防抖函数作如下改写:
function debounce(fn, wait) {
let timeout;
return function() {
let that = this;
let arg = arguments;
clearTimeout(timeout);
timeout = setTimeout(function(){
fn.apply(that,arg)//使用apply改变this指向
}, wait);
}
}
复制代码
固然了,若是使用箭头函数即可以省去外层声明。
以上的状况都是只有当连续触发中止后才执行,那若是咱们想让事件第一次触发就执行,后面的连续触发都不执行,直到中止触发一段时间才能够再次触发(好比防止频繁点击),该如何处理呢?
那么能够利用一样的原理,稍做修改便可:
function debounce(fn, wait) {
let timeout;
return function(){
let arg = arguments;
let that = this;
clearTimeout(timeout);
!timeout && fn.apply(that,arg)
timeout = setTimeout(function(){
timeout = null;
}, wait);
}
}
复制代码
顾名思义,节流就是节约流量,将连续触发的事件稀释成预设评率。 好比每间隔1秒执行一次函数,不管这期间触发多少次事件。
这有点像公交车, 不管在站点等车的人多很少,公交车只会按时来一班,不会来一我的就来一辆公交车。
function throttle(fn, wait) {
let timeout;
return function () {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
fn.apply(this, arguments)
}, wait)
}
}
}
复制代码
用滚动事件来描述节流,实际上是一个很是典型的场景,好比须要用滚动事件判断是否加载更多等。
和防抖函数相似,以上的状况是先等待后触发,若是咱们想让事件先触发后等待,该如何处理呢?网上大部分文章都告诉你用时间戳的方式去实现,其实只要像防抖同样稍做修改便可实现。
function throttle(fn, wait) {
let timeout;
return function () {
if (!timeout) {
fn.apply(this, arguments)
timeout = setTimeout(() => {
timeout = null;
}, wait)
}
}
}
复制代码
这样,咱们就会发现第一次触发函数就会当即生效。
关于防抖与节流,lodash、underscore等工具库都有完善的实现能够直接用,本没有必要造轮子。本文的目的仅仅是为了将其主要思想和实现思路展示出来。更重要的,知道防抖和节流的本质后,就知道在什么时候使用防抖或者节流,什么时候先触发或后触发。不管需求如何改变,均可以灵活的运用。