JavaScript 防抖和节流

本文将介绍防抖和节流,两种在前端中节省资源的方法。

一、为什么需要防抖和节流?

在前端开发中,我们往往需要监听某个事件并做相应的 “处理”。但在实际的开发中,我们不需要始终对某个事件做处理,这是一件无用且浪费资源的事情。

此时,我们便可以使用防抖和节流,在尽量不影响监听的情况下,减少 “处理” 所耗费的资源。

假设这样一个场景,我们需要监听鼠标的移动,并将坐标显示在屏幕之中,如下:

显而易见的是,监听鼠标的移动全程无意义的。我们只需要获取鼠标最终到达的目标,或者每隔一段时间获取一次鼠标的坐标即可。

我们可以像上面那样,对鼠标的每次移动都做响应,因为我们现在的 “处理” 是不太耗费性能的。但如果我们需要监听坐标并发送网络请求呢?如果我们需要监听坐标并做复杂运算呢?在这种情况下,我们就会因为无用的 “处理” 而影响 Web 应用与服务器的运行。

二、防抖

1. 什么是防抖?

触发后等待 n 秒再执行方法,重复触发则重新计时。

2. 实现方法

(1) 等待 n 秒再执行

  • fun.apply(obj, ...参数) 的作用是:以 obj 为 this,以 ...参数 为参数,调用 fun() 方法

  • arguments:即方法的所有参数

    在这里,它代表的是:调用 return 回去的方法时传入的参数

  • fun.apply(this, arguments) 的作用是,使得无论在什么地方调用了 fun()fun() 的 this 都会指向调用者,且所有参数都会传入

1
2
3
4
5
6
7
8
9
function debounce (fun, delay) {
return function() {
let context = this
let args = arguments
setTimeout(() => {
fun.apply(context, args)
}, delay)
}
}

(2) 重复触发则重新计时

  • 通过 timeout 变量进行计时并调用
  • 以闭包的方式,防止外界污染计时器
1
2
3
4
5
6
7
8
9
10
11
const debounce = (fun, delay = 1000) => {
let timeout // 计时器
return function() {
let context = this
let args = arguments
clearTimeout(timeout)
timeout = setTimeout(() => {
fun.apply(context, args)
}, delay)
}
}

(3) 返回参数

  • 上面的做法并不支持获取返回值,如果需要获取返回值,可以在 debounce() 方法中获取执行结果并返回
  • 由于方法并不是同步执行(需要等待计时完成后再执行),因此可以使用 Promise 异步返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
const debounce = (fun, delay = 1000) => {
let timeout // 计时器
return function() {
let context = this
let args = arguments
return new Promise((resolve, reject) => {
clearTimeout(timeout)
timeout = setTimeout(() => {
resolve(fun.apply(context, args))
}, delay)
})
}
}

3. 用法

  • 调用 debounce() 方法获取 “经防抖处理后的方法”

    1
    let debounceFun = debounce(fun, 时长)
  • 在事件的处理处,用 “经防抖处理后的方法” 替代

    1
    2
    3
    element.addEventListener('事件', function (e) {
    debounceFun(e.target.value)
    })

如何保证一个方法一个计时器:

按用法要求,一个方法只应调用一次 debounce()。因此,计时器被创建,”经防抖处理后的方法” 被创建并返回。

此后再调用 “经防抖处理后的方法”,都会清空计时器,并计时准备执行。

三、节流

1. 什么是节流?

让函数在一段时间内只执行一次,有节制地执行。

2. 实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
const throttle = (fun, delay = 1000) => {
let flag = true // 是否可以执行
return function () {
if (!flag) {
return
}
flag = false
fun.apply(this, arguments)
setTimeout(() => {
flag = true
}, delay)
}
}

3. 用法

  • 调用 throttle() 方法获取 “经节流处理后的方法”

    1
    let throttleFun = throttle(fun, 时长)
  • 在事件的处理处,用 “经节流处理后的方法” 替代

    1
    2
    3
    element.addEventListener('事件', function (e) {
    throttleFun(e.target.value)
    })

参考