Luuman's Blog

因为有了危机感,所以会义无反顾。


  • Home

  • About

  • Archives

  • Search

ES6 节流和防抖详解

Posted on 2019-08-27 Edited on 2019-09-16 In Induce

自用笔记:在浏览器DOM事件里面,有一些事件会随着用户的操作不间断触发。比如:重新调整浏览器窗口大小(resize),浏览器页面滚动(scroll),鼠标移动(mousemove)。也就是说用户在触发这些浏览器操作的时候,如果脚本里面绑定了对应的事件处理方法,这个方法就不停的触发。
这并不是我们想要的,因为有的时候如果事件处理方法比较庞大,DOM 操作比如复杂,还不断的触发此类事件就会造成性能上的损失,导致用户体验下降(UI 反映慢、浏览器卡死等)。所以通常来讲我们会给相应事件添加延迟执行的逻辑。
debounce 与 throttle 是开发中常用的高阶函数,作用都是为了防止函数被高频调用,换句话说就是,用来控制某个函数在一定时间内执行多少次。

效果可视化

在此区域移动你的鼠标

节流概念(Throttle)

定义: 如果一个函数持续的,频繁地触发,那么让它在一定的时间间隔后再触发。由于执行速度过快,并携带相邻数据相差不大,节流可以高效的优化执行效果。

应用场景

scroll、touchmove

节流实现

首次执行

通过时间比对,屏蔽多次提交,缺点:最后一个动作不会被触发

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
function throttles(fn, wait = 100){
let last = 0;
console.log('节流函数 启动')
return function(){
let curr = +new Date();
// 强制转换为数字Number
if(curr - last > wait){
fn.apply(this, arguments);
last = curr;
}
}
}
let fun = new throttle(test, 2000)
function test(e) {
console.log(e)
}
let funss = (m, n) => Array.apply(null, new Array(m)).map(() => n++)
let arr = funss(100, 1)
arr.forEach(item => {
fun('funcs')
})
// 节流函数 启动
// funcs

window.onresize = throttle(test, 200);
window.onresize = function () {
fun('funcs')
}

首次不执行

首次不执行,delay时间内只执行滞后一次,通过判断timer,进行判断延迟执行,预执行期间,去除其他执行请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function throttles2(fn, delay = 100){
//首先设定一个变量,在没有执行我们的定时器时为null
let timer = null;
return function(){
//当我们发现这个定时器存在时,则表示定时器已经在运行中,需要返回
if(timer) return;
timer = setTimeout(() => {
fn.apply(this,arguments);
timer = null;
}, delay);
}
}
function test(e) {
console.log(e)
}
let fun = new throttle(test, 2000)
let funss = (m, n) => Array.apply(null, new Array(m)).map(() => n++)
let arr = funss(100, 1)
arr.forEach(item => {
fun('funcs')
})

throttle

优点:成功的优化最后一针的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function throttle (fn, wait = 250, options) {
let lastTime, timerId
return function () {
let context = options || this
let currentTime = +new Date
let args = arguments
if (lastTime && currentTime < lastTime + wait) {
clearTimeout(timeId)
timeId = setTimeout(function() {
lastTime = currentTime
fn.apply(context, args)
}, wait);
} else {
lastTime = currentTime
fn.apply(context, args)
}
}
}

lodash

缺点无法获取参数,时间比对问题,属于首次执行的防抖的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function throttle(fn, wait, options) {
wait = wait || 0;
var timerId, lastTime = 0;
function throttled() {
var currentTime = new Date();
if (currentTime >= lastTime + wait) {
fn();
lastTime = currentTime;
} else {
if (timerId) {
clearTimeout(timerId);
timerId = null;
}
timerId = setTimeout(function() {
fn()
}, wait);
}
}
return throttled;
}

underscore

underscore提供了一系列函数式接口

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
68
69
70
71
72
// 返回一个函数,只要这段时间被调戏就不会触发。函数停止后N毫秒触发,
debounce = function(func, wait, immediate) {
var timeout, result;
var later = function(context, args) {
timeout = null;
if (args) result = func.apply(context, args);
};
var debounced = restArgs(function(args) {
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(later, wait);
if (callNow) result = func.apply(this, args);
} else {
timeout = _.delay(later, wait, this, args);
}
return result;
});
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};
let fun = new debounce(test, 2000)
let funss = (m, n) => Array.apply(null, new Array(m)).map(() => n++)
let arr = funss(100, 1)
arr.forEach(item => {
fun('funcs')
})

// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
throttle = function(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : new Date();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null; //显示地释放内存,防止内存泄漏
};
var throttled = function() {
var now = new Date();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
};

防抖概念(Debounce)

定义: 如果一个函数在一段时间间隔中,持续地触发,那么只在它结束后过一段时间只执行一次。

注意:这里的抖动停止表示你停止了触发这个函数,从这个时间点开始计算,当间隔时间等于你设定时间,才会执行里面的回调函数。如果你一直在触发这个函数并且两次触发间隔小于设定时间,则一定不会到回调函数那一步。·

应用场景

input验证、搜索联想、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
function throttle(fn, wait = 100, options) {
let timerId, lastTime = 0;
console.log('节流函数 启动')
return function() {
let currentTime = new Date();
if (currentTime >= lastTime + wait) {
console.log(timerId, lastTime)
fn.apply(this, arguments);
lastTime = currentTime;
} else {
if (timerId) {
clearTimeout(timerId);
timerId = null;
}
timerId = setTimeout(() => {
console.log(timerId, lastTime)
fn.apply(this, arguments);
}, wait);
}
}
}
function test(e) {
console.log('test', e)
}
let fun = new throttle(test, 2000)
let funss = (m, n) => Array.apply(null, new Array(m)).map(() => n++)
let arr = funss(100, 1)
arr.forEach(item => {
console.log(item)
fun('funcs')
})

延迟执行最后一个指令

1
2
3
4
5
6
7
8
9
10
11
12
function debounce(fn, delay) {
let timerId;
return function (...args) {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
fn(...args);
timerId = null;
}, delay);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
function debounce (fn, wait, options) {
var timeout;
return function () {
var context = options || this, args = arguments;
var later = function () {
timeout = null;
fn.apply(context, args);
};
clearTimeout(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
function debounce(fn, wait, options) {
var timer = null;
var previous = null;
return function () {
var now = +new Date();
if ( !previous ) previous = now;
if ( options && now - previous > options ) {
fn();
// 重置上一次开始时间为本次结束时间
previous = now;
clearTimeout(timer);
} else {
clearTimeout(timer);
timer = setTimeout(function() {
fn();
previous = null;
}, 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
function debounce(fn, delay = 200, atBegin = true) {
let timer = null, last = 0,during;
return function () {
let self = this, args = arguments;
var exec = function () {
fn.apply(self, args);
}
if (atBegin && !timer) {
exec();
atBegin = false;
} else {
during = Date.now() - last;
if (during > delay) {
exec();
} else {
if (timer) clearTimeout(timer);
timer = setTimeout(function () {
exec();
}, delay);
}
}
last = Date.now();
}
}
1
2
3
4
5
6
7
8
9
// 避免在滚动时过分的更新定位
jQuery(window).on('scroll', _.throttle(updatePosition, 100));

// 点击后就调用 `renewToken`,但5分钟内超过1次。
var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
jQuery(element).on('click', throttled);

// 取消一个 trailing 的节流调用
jQuery(window).on('popstate', throttled.cancel);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 避免窗口在变动时出现昂贵的计算开销。
jQuery(window).on('resize', _.debounce(calculateLayout, 150));

// 当点击时 `sendMail` 随后就被调用。
jQuery(element).on('click', _.debounce(sendMail, 300, {
'leading': true,
'trailing': false
}));

// 确保 `batchLog` 调用1次之后,1秒内会被触发。
var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
var source = new EventSource('/stream');
jQuery(source).on('message', debounced);

// 取消一个 trailing 的防抖动调用
jQuery(window).on('popstate', debounced.cancel);
以上就是节流和防抖的全部介绍
# JavaScript
Hexo 添加搜索功能
大胆尝试 复杂动画效果
  • Table of Contents
  • Overview

Luuman

爱折腾,爱运动,更爱游离于错综复杂的编码与逻辑中,无法自拔。相信编程是一门艺术,自诩为游弋在代码里的人生。
92 posts
19 categories
36 tags
友情链接
  • 编程の趣
  • 翁天信
  • MOxFIVE
  • Meicai
  1. 1. 效果可视化
  2. 2. 节流概念(Throttle)
    1. 2.1. 应用场景
    2. 2.2. 节流实现
      1. 2.2.1. 首次执行
      2. 2.2.2. 首次不执行
    3. 2.3. throttle
    4. 2.4. lodash
    5. 2.5. underscore
  3. 3. 防抖概念(Debounce)
    1. 3.1. 应用场景
    2. 3.2. 首次立即执行
    3. 3.3. 延迟执行最后一个指令
广告位 广告位 广告位
© 2019 Luuman
Powered by Hexo v3.9.0
|
Theme – Nice.Gemini v7.3.0