JavaScript专题之跟着 underscore 学节流

简介: JavaScript 专题系列第二篇,讲解节流,带你从零实现一个 underscore 的 throttle 函数

1.png


JavaScript 专题系列第二篇,讲解节流,带你从零实现一个 underscore 的 throttle 函数


前言


《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;
        }
    }
}复制代码


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


container.onmousemove = throttle(getUserAction, 1000);复制代码


效果演示如下:


使用时间戳


我们可以看到:当鼠标移入的时候,事件立刻执行,每过 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)
        }
    }
}复制代码


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


使用定时器


我们可以看到:当鼠标移入的时候,事件不会立刻执行,晃了 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;
}复制代码


效果演示如下:


throttle3


我们可以看到:鼠标移入,事件立刻执行,晃了 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;
}复制代码


取消


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


// 第五版 非完整代码,完整代码请查看最后的演示代码链接
...
throttled.cancel = function() {
    clearTimeout(timeout);
    previous = 0;
    timeout = null;
}
...复制代码


注意


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


那就是 leading:falsetrailing: 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
});复制代码


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


演示代码


相关的代码可以在 Github 博客仓库 中找到


专题系列


JavaScript专题系列目录地址:github.com/mqyqingfeng…


JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。


如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。



目录
相关文章
|
1月前
|
JavaScript
JS封装节流函数
JS封装节流函数
28 0
|
1月前
|
JavaScript
JS中防抖和节流的区别是什么
JS中防抖和节流的区别是什么
28 0
|
1月前
|
JavaScript 前端开发 UED
【面试题】面试官:说说你对js中的 防抖 和 节流 的理解
【面试题】面试官:说说你对js中的 防抖 和 节流 的理解
|
6天前
|
前端开发 JavaScript Java
【JavaScript】JavaScript 防抖与节流:以游戏智慧解锁实战奥秘
【JavaScript】JavaScript 防抖与节流:以游戏智慧解锁实战奥秘
15 3
|
11天前
|
JavaScript 前端开发 UED
深入理解JavaScript中的节流与防抖技术
理解并合理运用节流与防抖技术,可以帮助我们优化事件处理函数的执行频率,从而提升应用的性能和用户体验。这两种技术通过减少不必要的计算和DOM操作,使得Web应用程序能够更加流畅地运行。 通过掌握防抖和节流的实现原理及应用场景,开发者可以更加灵活地编写高效且性能优化的代码,对于面对高频事件处理时尤其重要。在开发中合理选择使用防抖或节流,将直接影响到应用的响应性和效率。
13 1
|
1月前
|
JavaScript 前端开发 UED
js的防抖节流
js的防抖节流
18 1
|
1月前
|
JavaScript 前端开发 UED
js的节流
js的节流
18 0
|
1月前
|
测试技术
js_防抖与节流(闭包的使用)
js_防抖与节流(闭包的使用)
31 0
|
1月前
|
JavaScript 前端开发
【JavaScript】面试手撕节流
上篇我们讲了防抖,这篇我们就谈谈防抖的好兄弟 -- 节流。这里在老生常谈般的提一下他们两者之间的区别,顺带给读者巩固下。
60 3
|
1月前
|
JavaScript