要解决的问题
前端开发中,经常绑定一些持续触发的事件或异步执行的事件,例如:
1.select下拉框支持远程查询可选列表(异步请求获取数据作为选项列表)
2.用户注册时,手机号 email等格式验证。
3.鼠标在一块div上moverover时执行某些行为。
4.监听页面滚动执行某些行为。
当类似这些行为,不需要实时的去执行时,可以考虑采用防抖和节流的方式。
防抖和节流都是优化高频率执行js代码的一种手段。
防抖 debounce
防抖多用于频繁的触发一个事件,但只需要保证执行一次即可,之前的触发都可以忽略的场景。如上面示例1 2
立即执行: 触发事件后立即执行函数,开始计时,在n秒内再次触发事件,不执行函数,并且重新计时。n秒后触发事件,立即执行函数,开始计时。(保证立即执行,屏蔽频繁操作[不执行])
/*html*/
<input id="search" placeholder='输出关键字查询匹配列表' />
<ul id="options"></ul>
/*js*/
let list = ['123','12345','1234567','123456789'];
var search = document.getElementById('search');
var options = document.getElementById('options');
let debounce_show = debounce(show, 1000);
search.addEventListener('input', e => {
options.innerHTML = `<li>正在查询...</li>`;
let keyword = e.target.value;
debounce_show(keyword);
})
function show(val) {
let filterList = list.filter(v => {
return v.includes(val);
});
let filterHtml = '';
if (filterList.length == 0) {
filterHtml = `<li>未匹配到数据</li>`;
} else {
filterList.forEach(v => {
filterHtml += `<li>${v}</li>`;
});
}
options.innerHTML = filterHtml;
}
/*
func 需要防抖的函数
wait 防抖时间(秒)
*/
function debounce(func, wait) {
let timeout = null;
return function (...args) {
let context = this;
if (timeout) {
clearTimeout(timeout); // 只是取消计时,timeout值没变,typeof timeout === 'number'
}
if (!timeout) {
func.apply(context, args);
}
timeout = setTimeout(() => {
timeout = null;
}, wait);
}
}
非立即执行: 触发事件后开始计时,在n秒后执行函数,如果在n秒内再次触发事件,则重新计时,保证等待n秒才执行函数。(延迟执行,频繁操作不断覆盖,保证执行最后的一次)
// 修改上面的debounce为:
function debounce(func, wait) {
let timeout = null;
return function (...args) {
let context = this;
if (timeout) {
clearTimeout(timeout); // 只是取消计时,timeout值没变,typeof timeout === 'number'
}
timeout = setTimeout(() => {
func.apply(context, args);
timeout = null;
}, wait);
}
}
节流 throttle
节流多用于频繁触发一个事件,但频率不必太高,希望控制频率以减少内存消耗的场景。如上面示例3 4
与防抖延迟执行以避免频繁执行不同,节流允许频繁执行,但可以限制触发频率。
也就是重复触发事件时,不重新计时,只判断是否允许执行。
节流原理: 设置一个空闲时间n秒,触发事件执行方法后,开始计时,n秒内再次触发事件不执行方法,n秒后触发事件才允许执行。
同样可以定义立即执行和非立即执行
/*html*/
<div style="height:4000px;">
<div id="time" style="position: fixed;"></div>
</div>
/*js*/
let throttle_showTime = throttle(showTime,1000);
document.addEventListener('scroll', e=>{
console.log('scroll')
throttle_showTime();
});
function showTime() {
let now = new Date();
let time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`;
document.getElementById('time').innerText = time;
}
/* 合并版的节流方法
func 需要节流的函数
wait 允许触发的最小间隔(秒)
immediate 是否立即执行
*/
function throttle(func, wait, immediate=true) {
let timeout = null;
return function (...args) {
let context = this;
if (!timeout) {
if (immediate) { // 立即执行版本
func.apply(context, args);
timeout = setTimeout(() => {
timeout = null;
}, wait);
} else { // 非立即执行版本
timeout = setTimeout(() => {
func.apply(context, args);
timeout = null;
}, wait);
}
}
}
}
上面代码运行后,在页面中一直滚动,可以根据输出的时间看出执行的频率为1秒1次
也可以用时间戳来实现,具体见参考的文章。
参考:
JavaScript专题之跟着 underscore 学防抖
JavaScript专题之跟着 underscore 学节流