我们前端在实际开发过程中,经常会遇到如下问题:
- 页面的
scroll事件 input输入检测事件- 高频点击提交
如果不做任何处理的话,页面可能会卡顿,性能较低。这时候就需要函数防抖和节流来出马啦。
1. 函数防抖
1.1 定义
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
例子:电梯是在等人进入10秒后自动关闭。如果电梯进人后10s内再次有人进入,则又得等10秒钟电梯才会自动关闭。如果中间一直进人,就一直不会关闭。
1.2 代码实现
思路:用定时器执行函数,判断如果存在定时器任务,则清空定时器,重新开启一个新的定时器
简易代码:
// fn 是需要执行的函数, interval 是等待时间间隔
function debounce(fn, interval = 300) {
let timeout = null
return function() {
clearTimeout(timeout)
timeout = setTimeout(()=> {
fn.apply(this, arguments)
}, interval)
}
}
但是这一版防抖有个缺点,就是第一次不会执行,得等到 interval 后才会执行,如果需要首次执行,可修改代码如下:
// immediate 为 true 代表需要立即执行 fn, 为 false 代表不需要
function debounce(fn, interval = 1000, immediate) {
let timeout = null
let first = true
return function() {
clearTimeout(timeout)
if (immediate && first) {
fn.apply(this, arguments)
first = false
}
timeout = setTimeout(()=> {
fn.apply(this, arguments)
}, interval)
}
}
1.3 使用
document.querySelector('body').addEventListener('mousemove', debounce((e) => {
console.log(e.clientY)
}, 300, true))
如图,对 mousemove事件防抖:
2. 函数节流
2.1 定义
规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。
例子: 在页面滚动时,会不断地触发 scroll 事件,实际上我们不需要这么频繁地去执行函数,这是就可以设置一个间隔,比如说 300ms,在 300ms 这个时间内,scroll 内的函数只能执行一次。
2.2 代码实现
思路 1:用时间轴来判断,如果事件触发时间与上一次函数执行时间之差大于规定时间,则执行一次,反之不执行
代码 1:
// fn 是需要执行的函数, interval 是等待时间间隔
function throttle(fn, interval = 1000) {
let pre = 0
return function() {
const now = Date.now()
if (now - pre > interval) {
fn.apply(this, arguments)
pre = now
}
}
}
这个函数特点是:会立刻执行,停止触发后就不会执行。
思路 2:用定时器,在定时器未执行的时间内,再次触发的函数将不再执行。
代码 2:
// fn 是需要执行的函数, interval 是等待时间间隔
function throttle(fn, interval = 1000) {
let canRun = true
return function() {
if (!canRun) {
return
}
canRun = false
setTimeout(() => {
fn.apply(this, arguments)
canRun = true
}, interval)
}
}
这个函数特点是:等待 interval 时间间隔后才执行,停止触发后会再执行一次。
把思路 1 和思路 2 结合起来,用变量来控制第一次是否执行及停止触发后是否再执行一次:
// options 作为第三个参数,
// options.leading 为 true 时表示第一次就执行,
// options.trailing 为 true 是表示停止触发时再执行一次
function throttle(fn, interval = 1000, options = {}) {
let pre = 0
let canRun = true
let first = options.leading || false
return function() {
if (first) {
fn.apply(this, arguments)
first = false
}
if (options.trailing) {
if (!canRun) {
return
}
canRun = false
setTimeout(() => {
fn.apply(this, arguments)
canRun = true
}, interval)
} else {
const now = Date.now()
if (now - pre > interval) {
if (pre) {
fn.apply(this, arguments)
}
pre = now
}
}
}
}
2.3 使用
document.querySelector('body').addEventListener('mousemove', throttle((e) => {
console.log(e.clientY)
}, 1000, {
leading: true,
trailing: true
}))
如图,对 mousemove事件节流: