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);

    总结

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

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

    相关文章和代码

  • 相关阅读:
    Java接口(interface),扫盲贴
    Java抽象类,扫盲贴
    Java类的继承、super关键字、复写
    Java内部类,扫盲贴
    数据结构学习笔记1--简单排序
    7.1 通用的职责分配软件原则 GRASP原则一: 创建者 Creator
    6.6 面向对象设计
    6.5 开始进入设计 … Transition to Design
    6.4 操作契约 Operation Contracts
    6.3 契约式设计
  • 原文地址:https://www.cnblogs.com/huenchao/p/6045459.html
Copyright © 2011-2022 走看看