js 防抖 节流 JavaScript
实际工作中,通过监听某些事件,如 scroll 事件检测滚动位置,根据滚动位置显示返回顶部按钮;如 resize 事件,对某些自适应页面调整 DOM 的渲染;如 keyup 事件,监听文字输入并调用接口进行模糊匹配等等,这些事件处理函数调用的频率如果太高,会加重浏览器的负担,减弱性能,造成用户体验不好。此时需要采用 debounce(防抖)和 throttle(节流)的方式来减少调用频率,同时不影响原来效果。
函数防抖(debounce)
当持续触发事件时,一段时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前就触发了事件,延时重新开始。函数防抖的应用场景,最常见的就是用户注册时候的手机号码验证和邮箱验证了。只有等用户输入完毕后,前端才需要检查格式是否正确,如果不正确,再弹出提示语。
上图中,持续触发 scroll 事件时,并不执行 handle 函数,当 1000 毫秒内没有触发 scroll 事件时,才会延时触发 scroll 事件;上面原理:对处理函数进行延时操作,若设定的延时到来之前,再次触发事件,则清除上一次的延时操作定时器,重新定时。代码如下:
// 函数防抖
var timer = false;
document.getElementById(“debounce”).onscroll = function(){
clearTimeout(timer); // 清除未执行的代码,重置回初始化状态
timer = setTimeout(function(){
console.log(“ 函数防抖 ”);
}, 1000);
};
防抖函数的封装使用
/**
* 防抖函数
* @param method 事件触发的操作
* @param delay 多少毫秒内连续触发事件,不会执行
* @returns {Function}
*/
function debounce(method,delay) {
let timer = null;
return function () {
let self = this,
args = arguments;
timer && clearTimeout(timer);
timer = setTimeout(function () {
method.apply(self,args);
},delay);
}
}
window.onscroll = debounce(function () {
let scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log(‘ 滚动条位置:’ + scrollTop);
},1000)
另一种写法
// 防抖
function debounce(fn, wait) {
var timeout = null;
return function() {
if(timeout !== null) clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
// 处理函数
function handle() {
console.log(“ 函数防抖 ”);
}
// 滚动事件
window.addEventListener(‘scroll’, debounce(handle, 1000));
函数节流(throttlo)
当持续触发事件时,保证一定时间段内只调用一次事件处理函数。函数节流应用的实际场景,多数在监听页面元素滚动事件的时候会用到。
上图中,持续触发 scroll 事件时,并不立即执行 handle 函数,每隔 1000 毫秒才会执行一次 handle 函数;函数节流的要点是,声明一个变量当标志位,记录当前代码是否在执行。如果空闲,则可以正常触发方法执行。代码如下:
// 函数节流 定时器
var canRun = true;
document.getElementById(“throttle”).onscroll = function(){
if(!canRun){
// 判断是否已空闲,如果在执行中,则直接 return
return;
}
canRun = false;
setTimeout(function(){
console.log(“ 函数节流 ”);
canRun = true;
}, 300);
};
节流函数的封装使用
// 节流 throttle 代码(时间戳)
var throttle = function(func, delay) {
var prev = Date.now();
return function() {
var context = this;
var args = arguments;
var now = Date.now();
if (now – prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
function handle() {
console.log(“ 函数节流 ”);
}
window.addEventListener(‘scroll’, throttle(handle, 1000));
// 节流 throttle 代码(定时器)
var throttle = function(func, delay) {
var timer = null;
return function() {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function() {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
function handle() {
console.log(“ 函数节流 ”);
}
window.addEventListener(‘scroll’, throttle(handle, 1000));
// 节流 throttle 代码(时间戳 + 定时器):
var throttle = function(func, delay) {
var timer = null;
var startTime = Date.now();
return function() {
var curTime = Date.now();
var remaining = delay – (curTime – startTime);
var context = this;
var args = arguments;
clearTimeout(timer);
if (remaining <= 0) {
func.apply(context, args);
startTime = Date.now();
} else {
timer = setTimeout(func, remaining);
}
}
}
function handle() {
console.log(“ 函数节流 ”);
}
window.addEventListener(‘scroll’, throttle(handle, 1000));
用时间戳 + 定时器,当第一次触发事件时马上执行事件处理函数,最后一次触发事件后也还会执行一次事件处理函数