`
Coding扣钉
  • 浏览: 3364 次
文章分类
社区版块
存档分类
最新评论

浅谈 Underscore.js 中 _.throttle 和 _.debounce 的差异

阅读更多

Underscore.js是一个很精干的库,压缩后只有5.2KB。它提供了几十种函数式编程的方法,弥补了标准库的不足,大大方便了JavaScript的编程。

 

本文仅探讨Underscore.js的两个函数方法 `_.throttle` 和 `_.debounce` 的原理、效果和用途。

 

通常的函数(或方法)调用过程分为三个部分:请求、执行和响应。(文中“请求”与“调用”同义,“响应”与“返回”同义,为了更好的表述,刻意采用请求和响应的说法。)

 

某些场景下,比如响应鼠标移动或者窗口大小调整的事件,触发频率比较高。若稍处理函数微复杂,需要较多的运算执行时间,响应速度跟不上触发频率,往往会出现延迟,导致假死或者卡顿感。

 

在运算资源不够的时候,最直观的解决办法就是升级硬件,诚然通过购买更好的硬件可以解决部分问题,但是也需要为此付出高额的成本。特别是客户端和服务器模式,要求客户端统一升级硬件基本不可能。

 

在资源有限的前提下,处理函数无法即时响应高频调用。退而求其次,只响应部分请求是否可行呢?某些场景下的密集性请求,具备很强的同质和连续性。比如说,鼠标移动的轨迹参数。响应越及时效果越平滑,但是如果响应速度跟不上时,反而会出现卡顿感,如果适当的丢弃一些请求效果更流畅。

 

throttle 和 debounce 是解决请求和响应速度不匹配问题的两个方案。二者的差异在于选择不同的策略。

 

电梯超时

 

想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应。假设电梯有两种运行策略 throttle 和 debounce ,超时设定为15秒,不考虑容量限制。

 

  • throttle 策略的电梯。保证如果电梯第一个人进来后,15秒后准时运送一次,不等待。如果没有人,则待机。
  • debounce 策略的电梯。如果电梯里有人进来,等待15秒。如果又人进来,15秒等待重新计时,直到15秒超时,开始运送。

 

使用示例

 

 _.throttle 使用示例

function log( event ) {
  console.log( $(window).scrollTop(), event.timeStamp );
};
// 控制台记录窗口滚动事件,触发频率比你想象的要快
$(window).scroll( log );
// 控制台记录窗口滚动事件,每250ms最多触发一次
$(window).scroll( _.throttle( log, 250 ) );

_.debounce 使用示例

function ajax_lookup( event ) {

  // 对输入的内容$(this).val()执行 Ajax 查询

};

// 字符输入的频率比你预想的要快,Ajax 请求来不及回复。

$('input:text').keyup( ajax_lookup );

// 当用户停顿250毫秒以后才开始查找

$('input:text').keyup( _.debounce( ajax_lookup. 250 ) );

 

underscore源码注解

 

让我们来读读源码,探其究竟。基于开发版本(1.7.0)的源码,加上了一些注释以帮助理解。

 

_.throttle 方法源码

/**

 * 频率控制 返回函数连续调用时,func 执行频率限定为 次 / wait

 * 

 * @param  {function}   func      传入函数

 * @param  {number}     wait      表示时间窗口的间隔

 * @param  {object}     options   如果想忽略开始边界上的调用,传入{leading: false}。

 *                                如果想忽略结尾边界上的调用,传入{trailing: false}

 * @return {function}             返回客户调用函数   

 */

_.throttle = function(func, wait, options) {

  var context, args, result;

  var timeout = null;

  // 上次执行时间点

  var previous = 0;

  if (!options) options = {};

  // 延迟执行函数

  var later = function() {

    // 若设定了开始边界不执行选项,上次执行时间始终为0

    previous = options.leading === false ? 0 : _.now();

    timeout = null;

    result = func.apply(context, args);

    if (!timeout) context = args = null;

  };

  return function() {

    var now = _.now();

    // 首次执行时,如果设定了开始边界不执行选项,将上次执行时间设定为当前时间。

    if (!previous && options.leading === false) previous = now;

    // 延迟执行时间间隔

    var remaining = wait - (now - previous);

    context = this;

    args = arguments;

    // 延迟时间间隔remaining小于等于0,表示上次执行至此所间隔时间已经超过一个时间窗口

    // remaining大于时间窗口wait,表示客户端系统时间被调整过

    if (remaining <= 0 || remaining > wait) {

      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;

  };

};

 

 _.debounce 方法源码

/**

 * 空闲控制 返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行

 *

 * @param  {function} func        传入函数

 * @param  {number}   wait        表示时间窗口的间隔

 * @param  {boolean}  immediate   设置为ture时,调用触发于开始边界而不是结束边界

 * @return {function}             返回客户调用函数

 */

_.debounce = function(func, wait, immediate) {

  var timeout, args, context, timestamp, result;

 

  var later = function() {

    // 据上一次触发时间间隔

    var last = _.now() - timestamp;

 

    // 上次被包装函数被调用时间间隔last小于设定时间间隔wait

    if (last < wait && last > 0) {

      timeout = setTimeout(later, wait - last);

    } else {

      timeout = null;

      // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用

      if (!immediate) {

        result = func.apply(context, args);

        if (!timeout) context = args = null;

      }

    }

  };

 

  return function() {

    context = this;

    args = arguments;

    timestamp = _.now();

    var callNow = immediate && !timeout;

    // 如果延时不存在,重新设定延时

    if (!timeout) timeout = setTimeout(later, wait);

    if (callNow) {

      result = func.apply(context, args);

      context = args = null;

    }

 

    return result;

  };

};

 

 

可视化演示

 

示例中每一行都以30ms的速度绘制时间轴,第一行 Mousemove Events 是参考基准,以50ms每次的响应频率,在时间轴上输出循环可见 ASCII 码字符。

 

当鼠标进入左侧方型区域(mouseenter 事件)所有行开始绘制时间轴, 鼠标晃动(mousemove 事件)会在时间轴上绘制字符块,每个字符块表示事件被触发一次。为了展现延迟触发效果,相邻字符块的演示和文字是不同的。

 

顶部的两个按钮`每100毫秒触发1次`和`每200毫秒触发2次`演示以固定频率匀速触发事件的效果。

 

演示地址:http://throttle-debounce.coding.io/

源码地址:https://coding.net/u/duwan/p/throttle-debounce/

 

使用场景

 

只要牵涉到连续事件或频率控制相关的应用都可以考虑到这两个函数,比如:

 

  • 游戏射击,keydown 事件
  • 文本输入、自动完成,keyup 事件
  • 鼠标移动,mousemove 事件
  • DOM 元素动态定位,window 对象的 resize 和 scroll 事件

 

前两者 debounce 和 throttle 都可以按需使用;后两者肯定是用 throttle 了。如果不做过滤处理,每秒种甚至会触发数十次相应的事件。尤其是 mousemove 事件,每移动一像素都可能触发一次事件。如果是在一个画布上做一个鼠标相关的应用,过滤事件处理是必须的,否则肯定会造成糟糕的体验。

 

 参考阅读

1. UNDERSCORE.JS

2. 高阶函数 debounce 和 throttle

3. jQuery throttle / debounce: Sometimes, less is more!

4. Debounce and Throttle: a visual explanation 

 

 

  • 大小: 130.9 KB
分享到:
评论

相关推荐

    underscore.js帮助文档

    Underscore.js是一款轻量级的JavaScript实用库,它提供了许多功能强大的函数,用于处理数组、对象、函数等,极大地增强了JavaScript的编程体验。在深入理解这个库之前,我们需要了解其核心理念:提供一套一致的方法...

    javascript函数式编程 underscore.js

    Underscore的`_.throttle`和`_.debounce`函数实现了这一概念,限制了函数调用的频率。 7. **部分应用(Partial Application)**:预先设置函数的部分参数,创建一个新的函数。Underscore的`_.partial`函数就是用于...

    underscore-1.6.0.zip

    underscore.js是一款轻量级的JavaScript实用库,它为JavaScript开发提供了大量的实用函数,极大地增强了原生JavaScript对象的处理能力。在"underscore-1.6.0.zip"这个压缩包中,包含的就是underscore库的1.6.0版本。...

    underscore应用

    underscore.js由Jeremy Ashkenas开发,最初是为了配合CoffeeScript语言使用而设计,后来因其简洁和强大的功能,逐渐成为JavaScript社区中的热门库。它不依赖任何其他库,可以独立使用,同时也可与jQuery、Backbone....

    微信小程序demo:使用第三方模块Underscore.js,Immutable.js,UUID.zip

    本示例中的“微信小程序demo”就展示了如何使用两个常用的JavaScript库——Underscore.js和Immutable.js,并且生成了一个UUID。下面将详细介绍这三个库及其在微信小程序中的应用。 **Underscore.js** Underscore.js...

    underscore-basic-tutorial:underscore.js的基础教程

    在这个基础教程中,我们将深入理解Underscore.js的核心概念和常用方法。 一、简介 Underscore.js由Jeremy Ashkenas 创建,它的设计灵感来源于Ruby语言,旨在使JavaScript编程更加简洁和高效。这个库的目标是通过...

    Underscore ,是js的一个实用库

    Underscore支持函数式编程,如`_.bind()`可以绑定函数到特定上下文,`_.compose()`用于组合多个函数,`_.after()`可以创建一个新函数,在原始函数被调用指定次数后才执行,`_.throttle()`和`_.debounce()`用于限制...

    深入解析Backbone.js框架的依赖库Underscore.js的作用

    Backbone.js 是一个用于构建富客户端应用的JavaScript框架,而Underscore.js 提供了丰富的实用函数,帮助开发者更高效地处理数据和对象。本文将详细介绍Underscore.js 的核心功能和其在Backbone.js 中的角色。 ### ...

    underscore-1.4.3.zip

    3. **函数工具**:Underscore.js支持函数的延迟执行、节流和防抖,以及提供`bind`(绑定上下文)、`compose`(函数组合)等高阶函数,使得函数式编程在JavaScript中更加便捷。 4. **实用函数**:此外,Underscore....

    underscore源码学习计划

    Underscore.js,作为一个轻量级的JavaScript实用库,为开发者提供了丰富的函数式编程工具,帮助我们处理日常开发中的各种问题。其简洁的代码和强大的功能使得它在JavaScript社区中广受欢迎。本篇文章将围绕...

    backbound. underscore

    3. **函数操作**:underscore.js 提供了throttle、debounce、once、wrap等函数,这些函数与事件驱动和异步编程密切相关。比如,`_.throttle` 可以限制函数的执行频率,防止过于频繁的调用,`_.debounce` 用于延迟...

    underscore-analysis:underscore.js原始学习-js

    除此之外,underscore.js 还提供了诸如 `_.throttle` 和 `_.debounce` 之类的函数,用于限制函数的执行频率,这对于处理用户输入或者性能敏感的场景非常关键。`_.throttle` 保证在给定的时间间隔内最多执行一次函数...

    underscore.js

    underscore.js,作为一款轻量级的JavaScript库,它提供了一整套实用的功能,旨在简化和优化JavaScript开发中的常见任务。在本文中,我们将深入探讨underscore.js的核心特性、设计理念以及如何在实际项目中有效地利用...

    underscore_note:underscore.js原始代码阅读学习笔记

    2. **函数式编程模式**:underscore.js还引入了一些常见的函数式编程模式,如curry、partial、debounce和throttle等。这些模式可以优化函数调用,提高性能或者避免不必要的计算。例如,`_.debounce`用于创建一个函数...

    underscore.rar

    Underscore.js是一个轻量级的JavaScript库,它提供了一系列实用的功能,用于处理数组、对象、函数以及其他数据结构。这个库被广泛应用于前端开发,尤其是与jQuery和其他JavaScript库结合使用时,可以极大地增强代码...

    underscore-revised:underscore.js 在我的代码版本中被重写

    《深入浅出JavaScript:underscore.js重写解析》 在编程世界中,JavaScript作为一种广泛使用的脚本语言,尤其在Web开发领域扮演着至关重要的角色。它提供了丰富的库和框架,极大地提高了开发效率,其中就包括著名的...

    backbone:Backbone.js和underscore.js待办事项示例

    **在Backbone.js和Underscore.js结合使用中的示例** 在"backbone-master"这个项目中,开发者可能创建了一个待办事项应用。在这个应用中: 1. `Models`:定义了一个待办事项模型,包含了任务标题、描述、状态等属性...

    Javascript节流函数throttle和防抖函数debounce

    问题的引出  在一些场景往往由于事件频繁被触发,因而频繁地进行DOM操作、资源加载,导致UI停顿甚至浏览器崩溃。 在这样的情况下,我们实际上的需求大多为...例如 jquery中有throttle和debounce插件, underscore.j

    eb-api-1.0.30.zip

    今天我们将聚焦于一个名为"underscore-java"的开源项目,它是一个针对Java平台的实现,借鉴了JavaScript中的Underscore.js库的理念和功能。这个项目的最新版本为"eb-api-1.0.30.zip",它旨在弥补Java在某些特定功能...

Global site tag (gtag.js) - Google Analytics