zoukankan      html  css  js  c++  java
  • 浅析 JavaScript 的函数节流和去抖

    现代网页的实现上,会有很多交互上的优化,比如常见的 滚动加载 ,输入联想 等等。他们的实现思路很简单,以滚动加载而言,无非就是去是增加一个滚动的事件监听,每次滚动判断当前的元素是否已经滚动到了用户的可视区,然后根据判断结果来决定是否来加载相关数据。 输入联想也类似,无非就是修改一下监听的事件类型,和判断的相关逻辑。

    前端交互的优化上,我们总是需要需要事件监听来知道用户进行了什么操作,从而设定页面给出怎样的反应。但本文主要的内容不是讲如何具体的实现某个交互,而是对交互实现的依赖的事件监听那部分的优化。

    滚动加载图片的例子

    1
    2
    3
    4
    5
    6
    7
    8

    var onScroll = function() {
    // 这里判断当前的 img 元素是否已经滚动到了用户的可视区
    // 根据判断结果来决定是否来加载相关数据
    // 注:img 元素当设置 src 属性值后会产生一个 http 的请求,加载图片
    };

    window.addEventListener('scroll',onScroll ,false);

    这样理论上就实现了图片的滚动加载或者说是按需加载,懒加载等等。

    这样的实现乍看没有任何问题,但在实际的场景中,问题还是很严重的。

    如下:

    很明显,当你在 codepen 中滚动鼠标的时候,显示的数字蹭蹭蹭的加大, 这个数字就是过程中,浏览器触发 scroll 事件的数字, 可以看出,浏览器触发 scroll 事件是很频繁的。

    所以,回到上面提出的问题中,因为浏览器触发 scroll 会很平凡, 也就意味着事件的监听回调函数 onScroll 会被很频繁的执行。这就是问题之所在,且不说我们的 onScroll 里面的逻辑可能会是大量的计算。如此频繁的计算就算可以很轻松的被完成,结果也是没有意义的。所以我们要在这里来优化代码,倒不是优化 onScroll 本省,而是想办法让 onScroll 执行的次数可以减少。

    函数节流

    在原本的逻辑中,假设在滚动条连续的滚动中,浏览器每 10ms 触发一次scroll 事件,就意味着浏览器每 10ms 就要去执行一次 onScroll,如果想让 onScroll 不那么频繁的被执行,怎么办?

    很明显,想去改动 scroll 触发的频率在前端这一层面上肯定是不行,因为他们浏览器自身的设定。那么是否可以在触发 scroll 事件之后,不直接调用 onScroll 函数从而进行优化呢?比如,假设scroll 事件持续触发,我们让 onScroll 函数每 250ms 触发。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    var throttle = function() {
    var previous = 0; // 初始设置上一次调用 onScroll 函数时间点为 0。
    var timeout;
    var wait = 250; // 该变量表示执行 onScroll 函数的间隔时间。
    // 返回一个函数 ,该函数在每次触发 scroll 事件时,真正调用的函数。
    return function() {
    var now = Date.now(); // 记录下当前触发 scroll 的时间
    var remaining = wait - (now - previous); // 持续的滚动过程中,计算得到调用 onScroll 的甚于时间
    if (remaining <= 0) {
    if (timeout) {
    clearTimeout(timeout)
    }
    previous = now;
    timeout = null;
    onScroll();
    } else if(!timeout) { // 初始或者非持续滚动(滚动动作的时间小于 250ms ),设定一个定时器。
    timeout = setTimeout(onScroll, wait);
    }
    }
    };

    // 注意这里的调用方式
    window.addEventListener('scroll',throttle() ,false);

    这样调用是不会有结果的

    1
    window.addEventListener('scroll',throttle ,false);

    上述的代码旨在说明实现函数节流的思路,著名的 underscore.js 和 lodash 都有节流方法的实现。

    输入联想的例子

    当你在使用 google 搜索内容的时候,有些关键词输入到一般,谷歌会展示一个可选列表,是根据你当前输入的内容作出的一个猜测联想,如果有中目标的联想,你就可以不需要输入接下来的内容,直接选择自动补全就好。

    这种场景下,我们一般是去监听用户的 onKeyRress 或者 onKeyUp 等等键盘事件。在当用户输入一些文字的时候,我们一监听到键盘事件就将用户当前输入的内容去发送一个 ajax 查询请求显然是不行的。那么在节流函数内部去调用 ajax 请求呢?

    在这种场景下,其实函数节流也是不合适,因为,函数节流只是减小了函数执行的在事件触发过程中频率,将原来可能调用1000次减少到调用100次。然而对输入联想的场景来说,只要用户还在触发键盘事件,那么发送请求就是没有意义的。

    所以,我们现在需要的一种实现是应该是在用户停止键盘事件一段时间后,去发送一个请求。

    比如应该这么设计这个过程,用户在输入,用户输入有所停顿(也就是不再触发键盘事件),当这个事件大于 250ms 之后,去触发一个查询请求。 如果用户输入停顿的时间小于 250ms,不去发送查询请求。

    电梯模型

    输入联想的执行过程,应该和生活中的电梯执行过程是一样的。
    比如,当你正在座电梯,当电梯门正要关闭的时候,这是有一个同学又冲过来,按了一下电梯的按钮,这个电梯门会打开然后等待一定的时间,如果在等待的过程中,又有另外一位同学过来按了按钮,之前等待的时间清零,继续等待一定的时间,直到在等待的时间中,不在有别的人去按按钮。门才会关闭,然后上下运行。

    函数去抖

    了解这个过程之后,来简单的实现下这个过程。

    1
    2
    // 如下搜索控件
    <input type="search" name="search" />
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var sendAjax = function(obj) {
    // 发送请求
    };
    var debounce = function() {
    var wait = 2500;
    var timeout;
    return function () {
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(sendAjax, wait);
    }
    };
    var input = document.querySelector('input[name=search]');

    input.addEventLister('keyup', debounce(), false);

    总结

    函数节流和函数去抖的使用场景略有不同。

    函数节流:在事件触发过程中,减小相关回调函数的执行频率。
    函数去抖:在事件触发结束后一定时间后去执行事件回调函数,如果在这一定时间内又触发了相关事件,则不去触发事件回调函数,下一次执行事件回调函数时间仍是确定的一定时间。

    相关文章和代码

  • 相关阅读:
    PHP 开发 APP 接口 学习笔记与总结
    Java实现 LeetCode 43 字符串相乘
    Java实现 LeetCode 43 字符串相乘
    Java实现 LeetCode 43 字符串相乘
    Java实现 LeetCode 42 接雨水
    Java实现 LeetCode 42 接雨水
    Java实现 LeetCode 42 接雨水
    Java实现 LeetCode 41 缺失的第一个正数
    Java实现 LeetCode 41 缺失的第一个正数
    Java实现 LeetCode 41 缺失的第一个正数
  • 原文地址:https://www.cnblogs.com/huenchao/p/6045459.html
Copyright © 2011-2022 走看看