Jamey's Jamey's
首页
导航站
  • 学习专栏

    • 《HTML》笔记
    • 《CSS》笔记
    • 《JavaScript》笔记
    • 《Vue》笔记
    • 《Git》笔记
    • 《规范》笔记
    • 《软技能》笔记
    • 《面试》笔记
    • 《持续集成&交付&部署》笔记
  • 踩坑专栏

    • 《Element-UI 实践系列》笔记
    • 《移动端 实践系列》笔记
    • 《综合》笔记
  • 配置专栏

    • 《环境系列》笔记
  • 极空间

    • Docker
  • 影视

    • movie
  • 编辑器笔记

    • 开发编辑器
  • 浏览器笔记

    • Chrome
  • Mac笔记

    • Mac
  • 跨界学习

    • 运营
  • 破解合集

    • 破解
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 书单
    • 关于

Jamey

首页
导航站
  • 学习专栏

    • 《HTML》笔记
    • 《CSS》笔记
    • 《JavaScript》笔记
    • 《Vue》笔记
    • 《Git》笔记
    • 《规范》笔记
    • 《软技能》笔记
    • 《面试》笔记
    • 《持续集成&交付&部署》笔记
  • 踩坑专栏

    • 《Element-UI 实践系列》笔记
    • 《移动端 实践系列》笔记
    • 《综合》笔记
  • 配置专栏

    • 《环境系列》笔记
  • 极空间

    • Docker
  • 影视

    • movie
  • 编辑器笔记

    • 开发编辑器
  • 浏览器笔记

    • Chrome
  • Mac笔记

    • Mac
  • 跨界学习

    • 运营
  • 破解合集

    • 破解
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 书单
    • 关于
  • 深入系列

  • 专题系列

    • 写在前面
    • JavaScript 专题之跟着 underscore 学防抖
    • JavaScript 专题之跟着 underscore 学节流
      • 📖. 前言
      • 一. 节流
      • 二. 使用时间戳
      • 三. 使用定时器
      • 四. 双剑合璧
      • 五. 优化
      • 六. 取消
      • 七. 注意
    • JavaScript 专题之数组去重
    • JavaScript 专题之类型判断(上)
    • JavaScript 专题之类型判断(下)
    • JavaScript 专题之深浅拷贝
    • JavaScript 专题之从零实现 jQuery 的 extend
    • JavaScript 专题之如何求数组的最大值和最小值
    • JavaScript 专题之数组扁平化
    • JavaScript 专题之学 underscore 在数组中查找指定元素
    • JavaScript 专题之 jQuery 通用遍历方法 each 的实现
    • JavaScript 专题之如何判断两个对象相等
    • JavaScript 专题之函数柯里化
    • JavaScript 专题之偏函数
    • JavaScript 专题之惰性函数
    • JavaScript 专题之函数组合
    • JavaScript 专题之函数记忆
    • JavaScript 专题之递归
    • JavaScript 专题之乱序
    • JavaScript 专题之解读 v8 排序源码
  • underscore系列

  • ES6系列

  • 模块化

  • 正则表达式

  • 单元测试

  • 微前端

  • 实用函数

  • Rollup

  • 解决方案

  • 《JavaScript》笔记
  • 专题系列
Jamey
2021-10-19
目录

JavaScript 专题之跟着 underscore 学节流

# JavaScript 专题之跟着 underscore 学节流

# 📖. 前言

在 《JavaScript专题之跟着underscore学防抖》 中,我们了解了为什么要限制事件的频繁触发,以及如何做限制:

  1. debounce 防抖
  2. throttle 节流

今天重点讲讲节流的实现。

# 一. 节流

节流的原理很简单:

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

根据首次是否执行以及结束后是否执行,效果有所不同,实现的方式也有所不同。

我们用 leading 代表首次是否执行,trailing 代表结束后是否再执行一次。

关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。

# 二. 使用时间戳

让我们来看第一种方法:使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。

看了这个表述,是不是感觉已经可以写出代码了…… 让我们来写第一版的代码:

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

例子依然是用讲 debounce 中的例子,如果你要使用:

container.onmousemove = throttle(getUserAction, 1000);
1

效果演示如下:

javascript_03-14_06

我们可以看到:当鼠标移入的时候,事件立刻执行,每过 1s 会执行一次,如果在 4.2s 停止触发,以后不会再执行事件。

# 三. 使用定时器

接下来,我们讲讲第二种实现方式,使用定时器。

当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。

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

为了让效果更加明显,我们设置 wait 的时间为 3s,效果演示如下:

javascript_03-14_07

我们可以看到:当鼠标移入的时候,事件不会立刻执行,晃了 3s 后终于执行了一次,此后每 3s 执行一次,当数字显示为 3 的时候,立刻移出鼠标,相当于大约 9.2s 的时候停止触发,但是依然会在第 12s 的时候执行一次事件。

所以比较两个方法:

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

# 四. 双剑合璧

那我们想要一个什么样的呢?

有人就说了:我想要一个有头有尾的!就是鼠标移入能立刻执行,停止触发的时候还能再执行一次!

所以我们综合两者的优势,然后双剑合璧,写一版代码:

// 第三版
function throttle(func, wait) {
  var timeout, context, args, result;
  var previous = 0;

  var later = function() {
    previous = +new Date();
    timeout = null;
    func.apply(context, args)
  };

  var throttled = function() {
    var now = +new Date();
    //下次触发 func 剩余的时间
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
     // 如果没有剩余的时间了或者你改了系统时间
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      func.apply(context, args);
    } else if (!timeout) {
      timeout = setTimeout(later, remaining);
    }
  };
  return throttled;
}
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

效果演示如下:

javascript_03-14_08

我们可以看到:鼠标移入,事件立刻执行,晃了 3s,事件再一次执行,当数字变成 3 的时候,也就是 6s 后,我们立刻移出鼠标,停止触发事件,9s 的时候,依然会再执行一次事件。

# 五. 优化

但是我有时也希望无头有尾,或者有头无尾,这个咋办?

那我们设置个 options 作为第三个参数,然后根据传的值判断到底哪种效果,我们约定:

  • leading:false 表示禁用第一次执行
  • trailing: false 表示禁用停止触发的回调

我们来改一下代码:

// 第四版
function throttle(func, wait, options) {
  var timeout, context, args, result;
  var previous = 0;
  if (!options) options = {};
  
  var later = function() {
    previous = options.leading === false ? 0 : new Date().getTime();
    timeout = null;
    func.apply(context, args);
    if (!timeout) context = args = null;
  };
  
  var throttled = function() {
    var now = new Date().getTime();
    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;
      func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
  };
  return throttled;
}
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

# 六. 取消

在 debounce 的实现中,我们加了一个 cancel 方法,throttle 我们也加个 cancel 方法:

// 第五版 非完整代码,完整代码请查看最后的演示代码链接
...
throttled.cancel = function() {
  clearTimeout(timeout);
  previous = 0;
  timeout = null;
}
...
1
2
3
4
5
6
7
8

# 七. 注意

我们要注意 underscore 的实现中有这样一个问题:

那就是 leading:false 和 trailing: false 不能同时设置。

如果同时设置的话,比如当你将鼠标移出的时候,因为 trailing 设置为 false,停止触发的时候不会设置定时器,所以只要再过了设置的时间,再移入的话,就会立刻执行,就违反了 leading: false,bug 就出来了, 所以,这个 throttle 只有三种用法:

container.onmousemove = throttle(getUserAction, 1000);
container.onmousemove = throttle(getUserAction, 1000, {
    leading: false
});
container.onmousemove = throttle(getUserAction, 1000, {
    trailing: false
});
1
2
3
4
5
6
7

至此我们已经完整实现了一个 underscore 中的 throttle 函数,恭喜,撒花!

#JavaScript 专题
上次更新: 2022/07/01, 17:34:19
JavaScript 专题之跟着 underscore 学防抖
JavaScript 专题之数组去重

← JavaScript 专题之跟着 underscore 学防抖 JavaScript 专题之数组去重→

Theme by Vdoing | Copyright © 2017-2023 Jamey | blog 闽ICP备19022664号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式