写这篇文章的原因其实是我发现以前还未完全掌握防抖和节流的概念。对!就是概念,这是面试的重点,也是实际项目中如何抉择的依据。
也可以把这篇文章当做这两篇文章的“后续”:
https://blog.csdn.net/qq_43624878/article/details/101155944
https://blog.csdn.net/qq_43624878/article/details/101687651
好,进入正题,时间很短,知识很重:
防抖
防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
注意了:如果n秒内又被触发,则重新计时。
也就是说,他是通过“ 清空setTimeout并重新计算 ”的方式运行的。
//防抖
let timer=null;
$(window).scroll(()=>{
if(timer){
clearTimeout(timer);
}
timer=setTimeout(()=>{
//延时200ms,处理滚动逻辑
},200);
})
这是我在小程序页面滚动中写的一段代码,他就完美实现了 停下之后再执行 这样一个解决程序因为监听严重耗费性能的功能。
适用场景:
- 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
- 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
面试手写版:
const debounce = (fn, delay) => {
let timer = null;
return (...args) => {
// 一旦调用(说明又被触发了),必先清空
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
};
那什么是 节流 ?
防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
这个就有意思了:在一定时间内,如果触发过,就不再执行相同代码段
//节流
let canRun=true;
window.addEventListener('resize',()=>{
if(!canRun){
return;
}
canRun=false;
setTimeout(()=>{
canRun=true;
//一些好玩的事情
},300);
});
这就涉及到setTimeout的执行机制了——JS单线程机制与异步编程!
也就是常说的 setTimeout是否按时结束?
其实,js中存在一个 事件循环机制 ,因为主线程一直有任务,直到setTimeout(fn,n)的n毫秒后,主线程才会放弃任务(或完成),然后立即去执行macrotask中的setTimeout回调任务。
js中还有一个 队列 :比如你执行setTimeout(task,100),其实只是确保这个任务,会在100ms后进入macrotask队列,但并不意味着他能立刻执行,可能当前主线程正在进行一个耗时的操作,也可能目前macrotask队列有很多任务,所以用setTimeout作为倒计时其实并不会保证准确。
而且,有一个setTimeout,就会进入一个队列,再进来,就会继续去“排队”,所以在上面的代码中,在前一次的setTimeout的300ms内,再变化也不会去执行setTimeout里面的事情。也就造成了“canRun为false”->“一定时间内只生效一次”的效果。
这段代码是我在所做的网站中实现的“监听网页宽度变化实现某些响应式功能”的功能。当然,这里只是简化版的。具体实现的太长了,我放到另一篇文章中了。
适用场景:
- 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
- 缩放场景:监控浏览器resize
- 动画场景:避免短时间内多次触发动画引起性能问题
防抖和节流的区别(图示):
参考文章:
移动开发适配绝招rem
扩展:任务队列
Macrotask常见任务:
- setTimeout
- setInterval
- setImmediate
- I/O
- 用户交互操作,UI渲染
Microtask常见任务:
- Promise
- process.nextTick(node.js)