前言
在前端开发中,我们经常会遇到一些频繁的事件触发,比如说 resize
、scroll
、mousemove
、keydown
等等这些事件,对于这些事件,我们一般会采取防抖和节流的方式来提高性能。
防抖
定义
防抖是指在一定时间内,无论触发多少次事件,都只执行一次事件处理函数。如果在设定的延迟时间内再次触发事件,那么会重新计算延迟时间。防抖常用于搜索框输入等场景,避免输入时的频繁请求。
示例代码
1
2
3
4
5
6
7
8
9
|
function debounce(fn, delay) {
let timer = null
return function() {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
|
这是一份非常经典的防抖代码,它的原理很简单,就是在一定时间内,如果再次触发事件,就清除上一次的定时器,重新计算延迟时间。
不过在这份代码里面还存在着许多考点,比如说 this
的指向问题,以及 arguments
的使用问题。
this 指向
在上面的代码中,我们使用了箭头函数,这样就可以保证 this
指向正确,但是如果我们不使用箭头函数,而是使用普通函数,那么 this
就会指向 window
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// <button>submit</button>
function debounce(fn, delay) {
let timer = null
return function() {
clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(this, arguments)
}, delay)
}
}
function submit() {
console.log(this)
}
const btn = document.querySelector('button')
btn.addEventListener('click', debounce(submit, 1000)) // 当触发点击事件时,控制台会打印 window 对象
|
这时候我们就需要使用 bind
来改变 this
的指向。
1
2
3
4
5
6
7
8
9
|
function debounce(fn, delay) {
let timer = null
return function() {
clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(this, arguments)
}.bind(this), delay) // 使用 bind 来改变 this 的指向
}
}
|
或者我们可以提前保存 this
的指向。
1
2
3
4
5
6
7
8
9
10
|
function debounce(fn, delay) {
let timer = null
return function() {
const that = this // 使用 that 来保存 this 的指向
clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(that, arguments)
}, delay)
}
}
|
arguments
arguments
的问题和 this
的问题类似,如果我们使用箭头函数,那么 arguments
就会指向外层函数的 arguments
,如果我们使用普通函数,那么 arguments
就会指向 setTimeout
里面的回调函数的 arguments
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// <button>submit</button>
function debounce(fn, delay) {
let timer = null
return function() {
clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(this, arguments)
}, delay)
}
}
function submit(e) {
console.log(e)
}
const btn = document.querySelector('button')
btn.addEventListener('click', debounce(submit, 1000)) // 当触发点击事件时,控制台会打印 undefined,因为 setTimeout 里面的回调函数没有参数
|
我们可以简单的用箭头函数来解决这个问题,也可以将 arguments
暂存起来。
1
2
3
4
5
6
7
8
9
10
|
function debounce(fn, delay) {
let timer = null
return function() {
const args = arguments // 暂存 arguments
clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(this, args)
}, delay)
}
}
|
节流
节流是指在一定时间内只允许事件处理函数执行一次。如果在设定的时间内再次触发事件,那么不会执行事件处理函数,直到下一个时间段。节流常用于滚动事件等场景,避免频繁触发导致的性能问题。
节流有两种实现方式,我们可以通过 时间戳 来实现,也可以通过 定时器 来实现。
时间戳
1
2
3
4
5
6
7
8
9
|
function throttle(fn, delay) {
let prev = Date.now()
return function() {
if (Date.now() - prev >= delay) {
fn.apply(this, arguments)
prev = Date.now()
}
}
}
|
定时器
1
2
3
4
5
6
7
8
9
10
11
|
function throttle(fn, delay) {
let timer = null
return function() {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
}
|
使用场景
防抖:
- 搜索框输入,防止用户输入过程中频繁请求接口。
- 按钮提交,防止用户多次点击提交按钮,重复提交表单。
- 浏览器窗口缩放,只需要等待窗口调整完成后,再执行计算事件。
节流:
- 滚动加载,滚动过程中,每隔一段时间触发一次事件,而不是在滚动过程中频繁触发事件。
- 鼠标移动,在处理鼠标移动事件时,也可以使用节流来限制事件处理函数的执行频率,提高性能。