# 防抖与节流
函数防抖和函数节流:优化高频率执行js代码的一种手段,js中的一些事件如浏览器的resize、scroll,鼠标的mousemove、mouseover,input输入框的keypress等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能。为了优化体验,需要对这类事件进行调用次数的限制。
# 防抖(debounce)
当持续触发事件时,一定时间内没有再触发事件,事件处理函数才会执行一次,如果设定的时间内触发了事件,就再重新开始延时。
应用场景:
搜索框输入框连续输入进行AJAX验证时,只有当一定时间内没有继续输入才开始查询,如果一段时间内又有了输入就重新 开始计时,防止输入一个字符就去查询一次,频繁请求,浪费性能。
监听scroll滚动事件,当滚动条一定时间内不再滚动了再执行事件。
监听resize窗口改变来调整样式时,用户不断调整窗口大小,当停下来时,再确定窗口大小改变布局。
拖拽dom功能move时,停止move时再执行某个事件。
实现:
利用 setTimeout函数来进行倒计时来做限定,每触发一次debounce事件,就执行一次setTimeout倒计时, 如果上一次倒计时没执行完又触发了debounce事件,就清除上一次的timer,然后重新倒计时。 直到最后一次限定时间后执行handle函数。
简易版:
var timer;
function debounce() {
if (timer) clearTimeout(timer);
timer = setTimeout(handle, 1000);
}
function handle() {
console.log(1);
}
window.addEventListener('scroll', debounce);
封装一下:把timer拿到函数内,通过闭包return一个函数,这个函数可以访问timer,这样每次执行的都是这个返回的函数,但是也能访问到公共的timer。
function debounce(fn, wait) {
var timer;
return function() {
if (timer) clearTimeout(timer);
timer = setTimeout(fn, wait);
}
}
function handle() {
console.log(1);
}
window.addEventListener('scroll', debounce(handle, 1000));
最终版: 可以传入参数
先把debounce
函数return的函数赋值给debounceCallback
,debounceCallback
保留着对debounce
的访问权,然后又能执行自己的操作。然后把参数传给debounceCallback
,这样args就是通过赋值后return返回的函数debounceCallback
传入的参数。然后再通过过apply传入参数借用执行debounce
传入的fn
函数。
function debounce(fn, wait) {
var timer;
return function () {
var _this = this;
var args = arguments; // 获取debounceCallback传入的参数
if (timer) clearTimeout(timer);
timer = setTimeout(function(){
fn.apply(_this, args); // 把参数传给fn
}, wait);
// timer = setTimeout(() => { fn(args) }, wait); // 使用箭头函数就不需要apply
}
}
function handle(arg) {
console.log(arg);
}
var debounceCallback = debounce(handle, 1000); // 通过此步赋值得到return的函数,然后把参数传入 巧妙之处
window.addEventListener('scroll', function(e) {
debounceCallback(e) // 传入参数
});
# 节流(throttle)
当持续触发事件时,让事件在一定的时间段内只执行一次函数。隔一段时间执行一次,减少执行的次数。但执行函数也是连续触发的, 只是频率变低。
应用场景:
滚动无限加载时,用户滚动页面,每隔一定时间加载一次数据。
输入框联想,用户一直输入,根据用户输入不停的提示联想词,这样使用节流,等到结束输入一定时间后,再去请求数据,这是防抖。
用户点击按钮,一直不停的点击,使用节流减少提交次数。
实现:
可以通过时间戳和定时器两种方法实现
1、通过时间戳节流
throttle事件触发,开始时记录时间戳,等到下一次执行throttle事件计算时间差,如果差值达到就执行一次函数。
简易版:
var start = 0;
function throttle() {
var end = Date.now();
if (end-start > 1000) {
console.log(1)
start = end
}
}
window.addEventListener('scroll', throttle);
封装版:
function throttle(fn, delay){
var start = Date.now();
return function(){
var _this = this;
var args = arguments;
var end = Date.now();
if(end - start > delay){
fn.apply(_this, args);
start = end;
}
}
}
function handle(arg) {
console.log(arg);
}
var throttleCallback = throttle(handle, 1000)
window.addEventListener('scroll', function(e) {
throttleCallback(e)
});
2、通过定时器节流
当throttle事件一直触发时,设置一个定时器,如果定时器存在就不执行handle函数,直到定时器结束,执行一次handle后清除定时器,然后再设置一次定时器,一定时间内只执行一次handle。
封装版:
function throttle(fn, delay){
var timer;
return function(){
var _this = this;
var args = arguments;
if(!timer) {
timer = setTimeout(function() {
fn.apply(_this, args);
timer = null
}, delay)
}
}
}
function handle(arg) {
console.log(arg);
}
var throttleCallback = throttle(handle, 1000)
window.addEventListener('scroll', function(e) {
throttleCallback(e)
});
防抖是有定时器就不执行;节流是你没定时器我就执行。
TIP
某些情况下我们可以用 requestAnimationFrame
这个API代替节流,相当于 throttle(handle, 16)
← 事件循环(Event Loop) 本地存储 →