防抖和节流

前言

在我们工作场景有很多频繁触发的,比如:

  1. window 事件:resize,scroll
  2. 鼠标事件:mouseMove,mousedown
  3. 键盘事件:keyup,keydown 等等

比如鼠标经过事件,如果你监听他去执行一个函数回调,你会发现一个鼠标滑过区块就会触发成百上千次函数,这对性能显然是个考验。

一般为了解决这个问题,我们有两个解决方案:

  1. debounce 防抖
  2. throttle 节流

防抖

防抖的原理:无论你触发多少次事件,我一定在事件触发 N 秒后才执行。如果你一直频繁操作,我就一直不触发,等你停止操作 N 庙后再去执行。

简单防抖

  1. // 第一版
  2. function debounce(func, wait) {
  3. var timeout;
  4. return function () {
  5. clearTimeout(timeout);
  6. timeout = setTimeout(func, wait);
  7. };
  8. }

绑定 this 和 event 事件,返回 result

在我们使用debounce函数的时候会把this绑定到windows对象上,这显然不是我们想要的

函数可能是有返回值的,我们也要考虑

另外事件传入我们也没有接受到,所以还要将参数传入

  1. function debouce(func, wait) {
  2. var timeout, result;
  3. return function () {
  4. var content = this;
  5. var args = arguments;
  6. clearTimeout(timeout);
  7. timeout = setTimeout(() => {
  8. result = func.apply(content, args);
  9. }, wait);
  10. return result;
  11. };
  12. }

立即执行

客户:哎 我怎么点半天都没有用啊,能不能给我点了就执行啊,害

得嘞

我们通过传入一个immediate来控制函数是否立即执行,

  1. function debouce(func, wait, immediate = false) {
  2. var timeout, result;
  3. var debounced = function () {
  4. var content = this;
  5. var args = arguments;
  6. if (timeout) clearTimeout(timeout);
  7. if (immediate) {
  8. //如果执行过,那么就不执行了
  9. var callNow = !timeout;
  10. timeout = setTimeout(() => {
  11. timeout = null;
  12. }, wait);
  13. if (callNow) result = func.apply(content, args);
  14. } else {
  15. timeout = setTimeout(() => {
  16. result = func.apply(content, args);
  17. }, wait);
  18. }
  19. };
  20. return debounced;
  21. }

考虑一下取消

  1. debounced.cancel = function () {
  2. clearTimeout(timeout);
  3. timeout = null;
  4. };

节流

节流的原理:如果你持续触发事件,每隔 N 秒,只执行一次事件

时间戳版本

时间戳版本原理:第一次拿到当前时间的时间戳并保存在闭包里,拿到下次执行的时间戳比较,如果大于等待时间就执行,并将时间更新。那么我们开始写代码。

  1. function throttle(func, wait) {
  2. var context, args;
  3. var previous = 0;
  4. var throttled = function () {
  5. context = this;
  6. args = arguments;
  7. var now = +new Date();
  8. if (now - previous > wait) {
  9. func.apply(context, args);
  10. previous = now;
  11. }
  12. };
  13. return throttled;
  14. }

定时器版本

定时器版本原理:和上面基本一致,就是变成通过一个标记timeout来记录是否有一个定时器在执行了,如果没有就执行一个新的定时器

  1. function throttle(func, wait) {
  2. var context, args;
  3. js;
  4. var timeout;
  5. var throttled = function () {
  6. context = this;
  7. args = arguments;
  8. if (!timeout) {
  9. timeout = setTimeout(() => {
  10. func.apply(context, args);
  11. timeout = null;
  12. }, wait);
  13. }
  14. };
  15. return throttled;
  16. }

所以比较两个方法:

  1. 第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行
  2. 第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件

合并版本

拥有执行和停止触发后还会执行 一次的特性

  1. function throttle(func, wait) {
  2. var context, args, timeout;
  3. var previous = 0;
  4. var throttled = function () {
  5. var now = +new Date();
  6. context = this;
  7. args = arguments;
  8. function later() {
  9. previous = +new Date();
  10. timeout = null;
  11. }
  12. //还有多少剩余时间
  13. let newDate = wait - (now - previous);
  14. if (newDate < 0 || newDate > wait) {
  15. if (timeout) {
  16. clearTimeout(timeout);
  17. timeout = null;
  18. }
  19. previous = now;
  20. func.apply(context, args);
  21. } else if (!timeout) {
  22. timeout = setTimeout(later, wait);
  23. }
  24. };
  25. return throttled;
  26. }

再加个取消就很完整了

扩展

// TODO:
可以使用 vue 写个函数防抖组件,

应用场景

debounce

  • search 搜索联想,用户在不断输入值时,用防抖来节约请求资源。
  • window 触发 resize 的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

throttle

  • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
  • 监听滚动事件,比如是否滑到底部自动加载更多,用 throttle 来判断