JS防抖和节流

防抖

原理

事件响应函数在一段时间后才执行,如果在这段时间内再次调用,则重新计算执行时间;当预定的时间内没有再次调用该函数,则执行响应逻辑

underscore中的debounce函数可以防抖

应用场景

  1. scroll事件滚动触发的时候
  2. 搜索框输入查询的时候
  3. 表单验证
  4. 按钮的提交事件
  5. 浏览器的窗口缩放,resize事件

自定义防抖函数

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
/**
* 防抖函数的自定义实现
* @param func 执行的函数
* @param wait 等待的时间
* @param immediate 是否立即执行
*/
function debounce (func, wait, immediate) {
// result -- 返回值
let timeout, result;
let debounced = function () {
let self = this
let args = arguments
if(timeout) clearTimeout(timeout)
if (immediate) {
// callNow是立即执行的变量
let callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
// 由于最开始timeout为undefined,取反为true,故立即执行
if (callNow) result = func.apply(self, args)
} else {
// 不会立即执行
timeout = setTimeout(function() {
//解决执行函数内部this指向问题以及event指向问题
result = func.apply(self, args)
}, wait)
}
return result
}
// 取消
debounced.cancel = function () {
clearTimeout(timeout)
timeout = null // 防止内存泄露
}
return debounced
}

节流

原理

如果你持续的触发事件,每隔一段时间,只执行一次事件

应用场景

  1. DOM元素的拖拽功能的实现
  2. 计算鼠标移动的距离
  3. 监听scroll滚动事件

初步实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 节流函数 -- 初步实现 -- 第一次会触发,最后一次不会触发
* @param func 执行的函数
* @param wait 等待的时间
*/
function trottle (func, wait) {
let context, args
// 之前的时间戳
let old = 0 // 默认为0
return function () {
context = this
args = arguments
// 获取当前的时间戳
let now = new Date().valueOf()
if (now-old > wait) {
// 立即执行
func.apply(context, args)
old = now
}
}
}

改进版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 节流函数 -- 第一次不会触发,最后一次会触发
*/
function trottle (func, wait) {
let context, args, timeout
return function () {
context = this
args = arguments
if (!timeout) {
timeout = setTimeout(() => {
timeout = null
func.apply(context, args)
}, wait)
}
}
}

再次改进版本

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
/**
* 节流函数
* 第一次会触发,最后一次会触发
*/
function trottle (func, wait) {
let context, args, timeout
let old = 0 // 时间戳
let later = function () {
old = new Date().valueOf()
timeout = null
func.apply(context, args)
}
return function () {
context = this
args = arguments
let now = new Date().valueOf()
if (now - old > wait) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
func.apply(context, args)
old = now
}

if (!timeout) {
timeout = setTimeout(later, wait)
}
}
}

最终版本

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
/**
* 节流函数
* 最终版本
* 第一次不会触发,最后一次会调用 leading:false, trailing:true
* 第一次会触发,最后一次不会触发 leading:true, trailing:false
*/
function trottle (func, wait, options) {
let context, args, timeout, result
let old = 0 // 时间戳
if (!options) options = {}
let later = function () {
old = new Date().valueOf()
timeout = null
result = func.apply(context, args)
}
let trottled = function () {
context = this
args = arguments
let now = new Date().valueOf()
if (options.leading === false && !old) {
old = now;
}
if (now - old > wait) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
result = func.apply(context, args)
old = now
}

if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, wait)
}
return result
}
trottled.cancel = function () {
clearTimeout(timeout)
timeout = null
}
return trottled
}