什么是防抖
防抖 (debounce
),顾名思义就是防止抖动。一些高频触发的事件( 如:resize
、scroll
、mousemove
…… )会导致事件处理函数高频执行。如果事件处理函数还操作DOM
,那就意味着会引发高频的渲染重绘或回流。极端情况下,就会看到明显的页面/元素抖动。
所以,防抖就是防止高频的 DOM
操作导致页面频繁的渲染。
现代浏览器内核针对这种情况,内部进行了优化:会设置一个时间阀值,把这个时间内的 DOM
改变合并渲染。即便如此,我们在实际的项目中也要做好防抖,尽量为内核减负。
什么是节流
节流 (throttle
),顾名思义就是节约流量(流量是网络世界宝贵的资源,web
又是流量的主要入口之一)。广义上也可以衍生为节约资源,这里的资源主要包括我们宝贵的浏览器内核资源。
所以,节流就是要避免不必要的网络请求,避免不必要的 js
执行和页面渲染。
明显,广义上的节流是包含防抖的,只是,防抖和节流在具体实现上是有差异的。
防抖和节流的区别
防抖和节流都需要设置一个时长
。
防抖:延时执行,并且
时长
内没有重复触发才会执行,否则重新计时。所以,最后(最新)一次事件必定响应。典型场景有:- 页面缩放(
resize
): 页面缩放的时候,动态调整某些元素的大小。典型的防抖场景。 - 搜索框联想(
change
): 连续输入,触发多次搜索。一方面浪费资源,另一方面,如果先联想的结果后返回,那么显示就不是最新匹配的。 - 文本编辑器(
change
): 实时保存的文本编辑器。问题同上。
- 页面缩放(
节流:高频事件在
时长
内处理一次即可,一般是这段时间的第一次。典型场景有:- 元素拖拽/缩放(
mousedown/mousemove
): 需要实时显示元素的位置/大小,但是频率也无需和事件触发频率一致。 - 提交按钮(
click
): 会发起网络请求的点击按钮。遇到暴力点击,不光有重复提交的问题,还会导致流量和浏览器内部资源大大浪费。
- 元素拖拽/缩放(
根据实际场景的需求来选择 防抖 还是 节流 。比如 元素拖拽/缩放,如果需求不要求过程只追求结果,就应该选择 防抖。
明显,我们可以通过时间戳,定时器来控制。下面,我们来看看防抖节流如何具体实现。
防抖函数
防抖的关键在于延迟执行,所以推荐使用定时器。
- 基础版: 延迟
ms
毫秒执行,在这期间的其他重复请求不执行。
function debounce(func, ms) {
let timeout;
return function () {
const context = this;
const args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, ms);
}
}
window.onmousemove = debouce(()=> console.log(1), 1000);
- 进阶版:+ 首次请求立即执行。
function debounce(func, ms, immediate) {
let timeout;
return function () {
const context = this;
const args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, ms)
if (callNow) func.apply(context, args)
} else {
timeout = setTimeout(function () {
func.apply(context, args)
}, ms);
}
}
}
节流函数
节流的关键在于控制一段时间内只执行一次,所以时间戳和定时器都可以。区别是时间戳版触发是在时间段内开始的时候,而定时器版触发是在时间段内结束的时候。
- 定时器版:每
ms
毫秒只执行一次。
function throttle(func, wait) {
let timeout;
return function () {
const context = this;
const args = arguments;
if (!timeout) {
timeout = setTimeout(function () {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
- 时间戳版:每
ms
毫秒只执行一次。
function throttle(func, wait) {
var previous = 0;
return function () {
var now = Date.now();
var context = this;
var args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}