资讯专栏INFORMATION COLUMN

JavaScript 节流函数 throttle 详解

instein / 1080人阅读

摘要:后续的一些辅助性优化读者可以自己琢磨,如函数指向,返回值保存等。

原文:https://keelii.github.io/2016/06/11/javascript-throttle/

在浏览器 DOM 事件里面,有一些事件会随着用户的操作不间断触发。比如:重新调整浏览器窗口大小(resize),浏览器页面滚动(scroll),鼠标移动(mousemove)。也就是说用户在触发这些浏览器操作的时候,如果脚本里面绑定了对应的事件处理方法,这个方法就不停的触发。

这并不是我们想要的,因为有的时候如果事件处理方法比较庞大,DOM 操作比如复杂,还不断的触发此类事件就会造成性能上的损失,导致用户体验下降(UI 反映慢、浏览器卡死等)。所以通常来讲我们会给相应事件添加延迟执行的逻辑。

通常来说我们用下面的代码来实现这个功能:

var COUNT = 0;
function testFn() { console.log(COUNT++); }
// 浏览器resize的时候
// 1. 清除之前的计时器
// 2. 添加一个计时器让真正的函数testFn延后100毫秒触发
window.onresize = function () {
    var timer = null;
    clearTimeout(timer);

    timer = setTimeout(function() {
        testFn();
    }, 100);
};

细心的同学会发现上面的代码其实是错误的,这是新手会犯的一个问题:setTimeout 函数返回值应该保存在一个相对全局变量里面,否则每次 resize 的时候都会产生一个新的计时器,这样就达不到我们发的效果了

于是我们修改了代码:

var timer = null;
window.onresize = function () {
    clearTimeout(timer);
    timer = setTimeout(function() {
        testFn();
    }, 100);
};

这时候代码就正常了,但是又多了一个新问题 —— 产生了一个全局变量 timer。这是我们不想见到的,如果这个页面还有别的功能也叫 timer 不同的代码之前就是产生冲突。为了解决这个问题我们要用 JavaScript 的一个语言特性:闭包 closures 。相关知识读者可以去 MDN 中了解,改造后的代码如下:

/**
 * 函数节流方法
 * @param Function fn 延时调用函数
 * @param Number delay 延迟多长时间
 * @return Function 延迟执行的方法
 */
var throttle = function (fn, delay) {
    var timer = null;

    return function () {
        clearTimeout(timer);
        timer = setTimeout(function() {
            fn();
        }, delay);
    }
};
window.onresize = throttle(testFn, 200, 1000);

我们用一个闭包函数(throttle节流)把 timer 放在内部并且返回延时处理函数,这样以来 timer 变量对外是不可见的,但是内部延时函数触发时还可以访问到 timer 变量。

当然这种写法对于新手来说不好理解,我们可以变换一种写法来理解一下:

var throttle = function (fn, delay) {
    var timer = null;

    return function () {
        clearTimeout(timer);
        timer = setTimeout(function() {
            fn();
        }, delay);
    }
};

var f = throttle(testFn, 200);
window.onresize = function () {
    f();
};

这里主要了解一点:throttle 被调用后返回的 function 才是真正的 onresize 触发时需要调用的函数

现在看起来这个方法已经接近完美了,然而实际使用中并非如此。举个例子:

如果用户 不断的 resize 浏览器窗口大小,这时延迟处理函数一次都不会执行

于是我们又要添加一个功能:当用户触发 resize 的时候应该 在某段时间 内至少触发一次,既然是在某段时间内,那么这个判断条件就可以取当前的时间毫秒数,每次函数调用把当前的时间和上一次调用时间相减,然后判断差值如果大于 某段时间 就直接触发,否则还是走 timeout 的延迟逻辑。

下面的代码里面需要指出的是:

previous 变量的作用和 timer 类似,都是记录上一次的标识,必须是相对的全局变量

如果逻辑流程走的是“至少触发一次”的逻辑,那么函数调用完成需要把 previous 重置成当前时间,简单来说就是:相对于下一次的上一次其实就是当前

/**
 * 函数节流方法
 * @param Function fn 延时调用函数
 * @param Number delay 延迟多长时间
 * @param Number atleast 至少多长时间触发一次
 * @return Function 延迟执行的方法
 */
var throttle = function (fn, delay, atleast) {
    var timer = null;
    var previous = null;

    return function () {
        var now = +new Date();

        if ( !previous ) previous = now;

        if ( now - previous > atleast ) {
            fn();
            // 重置上一次开始时间为本次结束时间
            previous = now;
        } else {
            clearTimeout(timer);
            timer = setTimeout(function() {
                fn();
            }, delay);
        }
    }
};

实践:

我们模拟一个窗口 scroll 时节流的场景,也就是说当用户滚动页面向下的时候我们需要节流执行一些方法,比如:计算 DOM 位置等需要连续操作 DOM 元素的动作

完整代码如下:




    
    throttle


    

我们用两个 case 来测试效果,分别是添加至少触发 atleast 参数和不添加:

// case 1
window.onscroll = throttle(testFn, 200);
// case 2
window.onscroll = throttle(testFn, 200, 500);

case 1 的表现为:在页面滚动的过程(不能停止)中 testFN 不会被调用,直到停止的时候会调用一次,也就是说执行的是 throttle 里面 最后 一个 setTimeout ,效果如图(查看原 gif 图):

case 2 的表现为:在页面滚动的过程(不能停止)中 testFN 第一次会延迟 500ms 执行(来自至少延迟逻辑),后来至少每隔 500ms 执行一次,效果如图

至此为止,我们想要实现的效果已经基本完成。后续的一些辅助性优化读者可以自己琢磨,如:函数 this 指向,返回值保存等。

引用:

测试代码 http://jsbin.com/tanuxegija/edit

完整版本代码 http://jsbin.com/jigozuvuko

Debounce VS throttle https://github.com/dcorb/debounce-throttle

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79772.html

相关文章

  • js 防抖 节流 JavaScript

    摘要:此时需要采用防抖和节流的方式来减少调用频率,同时不影响原来效果。函数防抖当持续触发事件时,一段时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前就触发了事件,延时重新开始。 js 防抖 节流 JavaScript 实际工作中,通过监听某些事件,如scroll事件检测滚动位置,根据滚动位置显示返回顶部按钮;如resize事件,对某些自适应页面调整DOM的渲染;如ke...

    int64 评论0 收藏0
  • JavaScript 函数节流 throttle 和防抖 debounce

    摘要:今天和别人聊到函数的节流和防抖,发现自己对这两个的区别很是模糊,遂小小实践一下,在此记录,希望对需要的人有所帮助。防抖实现顺利,但是两个节流方法的执行结果存在差异。 今天和别人聊到JavaScript函数的节流和防抖,发现自己对这两个的区别很是模糊,遂小小实践一下,在此记录,希望对需要的人有所帮助。 节流 - 频繁操作,间隔一定时间去做一件事 举例说明:假定时间间隔为 500ms,频繁...

    mmy123456 评论0 收藏0
  • JavaScript函数节流的理解

    摘要:每次鼠标移动都是这样的处理过程,直到鼠标不再移动一秒钟后,定时器中的函数才被使用。 函数节流的原理 函数节流,就是对会频繁触发的函数事件做一些限制,让这些函数可以在每隔一定的时间或者每次满足一定的条件下再触发。一般我们会给他起一个名字throttle。也就是节流的意思。一般这样的函数有 resize事件、ontouchmove事件等。 举个简单的例子 测试函数节...

    taohonghui 评论0 收藏0
  • JavaScript专题之跟着 underscore 学节流

    摘要:关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。 JavaScript 专题系列第二篇,讲解节流,带你从零实现一个 underscore 的 throttle 函数 前言 在《JavaScript专题之跟着und...

    JerryC 评论0 收藏0

发表评论

0条评论

instein

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<