防抖和节流

防抖(debounce)

setTimeout 方法

1
2
3
4
5
6
7
8
9
10
11
var debounce = function(fn, delayTime) {
var timeId; // 用于存储定时器
// 闭包 防止 timeId 被销毁
return function() {
var context = this, args = arguments; // args 存储传入的参数
timeId && clearTimeout(timeout); // 判断定时器是否存在,存在则删除定时器
timeId = setTimeout(function{ // 添加定时任务
fn.apply(context, args);
}, delayTime)
}
}

思路解析:
执行 debounce 函数之后会返回一个新的函数,通过闭包的形式,维护一个变量 timeId ,每次执行该函数的时候会结束之前的延迟操作,重新执行 setTimeout 方法,也就实现了上面所说的指定的时间内多次触发同一个事件,会合并执行一次。

温馨提示:
  1. 上述代码中arguments只会保存事件回调函数中的参数,譬如:事件对象等,并不会保存 fn、delayTime
  2. 使用 apply 改变传入的fn方法中的this指向,指向绑定事件的 DOM 元素。

节流(throttle)

时间戳

1
2
3
4
5
6
7
8
9
10
11
12
var throttle = (fn, delayTime) => {
var _start = Date.now(); // 获取当前时间戳
return function () {
var _now = Date.now(),
context = this,
args = arguments;
if (_now - _start >= delayTime) {
fn.apply(context, args);
_start = Date.now();
}
};
};

思路解析:我们设置了一个标志变量 flag,当 delayTime 之后执行事件回调,便会把这个变量重置,表示一次回调已经执行结束。

温馨提示:
  1. 上述代码中arguments只会保存事件回调函数中的参数,譬如:事件对象等,并不会保存 fn、delayTime
  2. 使用 apply 改变传入的fn方法中的this指向,指向绑定事件的 DOM 元素。

定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
var throttle = function (fn, delayTime) {
var flag;
return function () {
var context = this,
args = arguments;
if (!flag) {
flag = setTimeout(function () {
fn.apply(context, args);
flag = false;
}, delayTime);
}
};
};

定时器 + 时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var throttle = function (fn, delayTime) {
var flag,
_start = Date.now();
return function () {
var context = this,
args = arguments,
_now = Date.now(),
remainTime = delayTime - (_now - _start);
if (remainTime <= 0) {
fn.apply(this, args);
} else {
setTimeout(function () {
fn.apply(this, args);
}, remainTime);
}
};
};

requestAnimationFrame

1
2
3
4
5
6
7
8
9
10
11
var throttle = function (fn, delayTime) {
var flag;
return function () {
if (!flag) {
requestAnimationFrame(function() {
fn();
flag = false;
});
flag = true ;
}
}

保证在屏幕刷新的时候(对于大多数的屏幕来说,大约16.67ms),可以执行一次回调函数 fn。使用这种方式也存在一种比较明显的缺点,时间间隔只能跟随系统变化,我们无法修改,但是准确性会比 setTimeout 高一些。

温馨提示:
  1. 防抖和节流只是减少了事件回调函数的执行次数,并不会减少事件的触发频率。
  2. 防抖和节流并没有从本质上解决性能问题,我们还应该注意优化我们事件回调函数的逻辑功能,避免在回调中执行比较复杂的DOM操作,减少浏览器reflow和repaint。

underscore 源码

debounce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
_.debounce = function (func, wait, immediate) {
var timeout, result;
var later = function (context, args) {
timeout = null;
if (args) result = func.apply(context, args);
};
var debounced = restArguments(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;
};

throttle

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
_.throttle = function (func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function () {
previous = options.leading === false ? 0: _.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function () {
var now = _.now();
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;
};

以上是我对下列视频及文章的归纳和总结。
十分钟学会防抖和节流
函数防抖与函数节流

作者

Fallen-down

发布于

2020-06-14

更新于

2020-06-14

许可协议

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

评论

You forgot to set the shortname for Disqus. Please set it in _config.yml.
You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.