防抖与节流

防抖

浏览器中有的事件触发非常频繁,如果在事件触发的时候就调用,这时就会不断地产生新的调用,导致变‘卡’。
防抖就是在某段时间间隔内,不调用函数,直到一段时间后不在再新的事件触发,再调用函数;或者是先调用函数,在一段时间间隔内继续触发不再重复调用函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
let times = 0
let container = document.querySelector("#container")
let button = document.querySelector("#cancel")

function getUserAction() {
// do some thing
container.innerHTML = `run ${++times} time(s).`
}

/**
*
* @param {function(): *} func
* @param {number} wait
* @param {boolean} immediate
* @returns {function(): *}
*/
function debounce(func, wait, immediate) {
let timer // start a new timer every time it is triggered
let result // get the return from func | undefined(only if the immediate is true will return a value)

let debounced = function () {
let context = this // current context
let args = arguments // events

if (timer) {
clearTimeout(timer)
}
if (immediate) {
// check whether it is called immediately
let callNow = !timer // callNow is always false unless the timer is null
timer = setTimeout(() => {
timer = null // set the timer null after wait time
}, wait)
if (callNow) {
// means timer is null now, then call func
result = func.apply(context, args)
}
} else {
timer = setTimeout(() => {
// 1. bind the `this` to `<div id="container"></div>`
// 2. get the `event`
func.apply(context, args)
}, wait)
}

return result
}

debounced.cancel = function () {
// to cancel the debounce
clearTimeout(timer)
timer = null // reset to null
}

return debounced
}

let userAction = debounce(getUserAction, 5000, true) // get the function `debounced`

container.onmousemove = userAction
button.onclick = function () {
userAction.cancel()
}

节流

如果持续触发事件,那么每固定时间内只触发一次事件。实现方式可以使用时间戳,或者定时器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
let times = 0
let container = document.querySelector("#container")
let button = document.querySelector("#cancel")

function getUserAction() {
// do some thing
container.innerHTML = `run ${++times} time(s).`
}

function throttle(
func,
wait,
options = {
leading: true, // run immediately
trailing: true, // run after wait time
}
) {
let timer
let context
let args
let previous = 0

let later = function () {
previous = !options.leading ? 0 : new Date().getTime()
timer = null
func.apply(context, args)
context = args = null
}

let throttled = function () {
let now = new Date().getTime()
if (!previous && !options.leading) previous = now
let remaining = wait - (now - previous)
context = this
args = arguments

if (remaining <= 0 || remaining > wait) {
if (timer) {
clearTimeout(timer)
timer = null
}

previous = now
func.apply(context, args)
if (!timer) context = args = null
} else if (!timer && options.trailing) {
timer = setTimeout(later, remaining)
}
}

throttled.cancel = function () {
clearTimeout(timer)
previous = 0
timer = null
}

return throttled
}

let userAction = throttle(getUserAction, 5000, {
leading: true,
trailing: false,
})
// let userAction = throttle(getUserAction, 5000, {leading: false, trailing: true})

container.onmousemove = userAction
button.onclick = userAction.cancel