JS学习笔记之函数防抖与节流

在日常开发中,我们经常能够碰到以下工作场景:

    • 对提交按钮进行变态的点击压力测试
    • 输入框内容的实时校验(譬如验证用户名是否已存在)
    • 图片滚动加载scroll操作
    • 窗口放大缩小resize操作
    • 对某一区域进行mousemove操作

上述频繁触发事件的操作,如果我们不采取任何操作,势必会造成极差的用户体验。譬如,对提交按钮连续点击发起请求,会给服务器带来压力;窗口放大缩小,会连续触发浏览器的resize函数,如果涉及到大量的dom操作,势必又会引起页面的回流与重绘,有可能会让页面变得卡顿等等。
如果我们不想频繁的触发某一事件,这时候就可以考虑用函数防抖、函数节流了。

函数防抖(debounce)

原理:在规定的时间t内,如果连续触发某一事件,则不会调用事件回调函数;连续触发某一事件,t时间内,不再触发该事件,则执行事件回调函数。
我们以连续点击提交按钮为例:

  • 正常操作:
<button id="submit">提交</button>
<script>
    function doAjax() {
        console.log("Todo ajax...");
    }
    var btn = document.querySelector("#submit");
    btn.addEventListener("click", doAjax);
</script>
demo01.gif

在短时间内,我们对提交按钮连续点击,可以看到连续的请求被发起,我想这个时候后端的同事应该会泪流满面吧。

  • 使用防抖
<button id="submit">提交</button>
<script>
    function doAjax() {
        console.log("Todo ajax...");
    }

    function debounce(fn, delay) {
        var timer = null;
        return function() {
            timer && clearTimeout(timer);
            var context = this,         // 将执行环境指向当前dom
                arg = arguments;        // 事件e
            timer = setTimeout(function() {
                fn.call(context, arg);
            },delay);
        }
    }

    var btn = document.querySelector("#submit");
    btn.addEventListener("click", debounce(doAjax, 1000));
</script>
demo02.gif

我们可以观察到,连续点击提交按钮,并没有执行请求;隔了1s后,执行请求(也就是在这1s内,没有点击提交按钮),这样就很好的解决了我们的烦恼。

  • 防抖之立即执行
    上述的防抖函数,已经可以解决我们大部分场景下的问题,但有一个需要注意的点:点击该提交按钮,需要等一段时间后,才会调用函数。而我们工作当中的另外一种需求为:点击后立即执行,在接下来的连续触发中,不执行事件回调函数
<button id="submit">提交</button>
<script>
    function doAjax() {
        console.log("Todo ajax...");
    }

    function debounce(fn, delay, isImmediate) {
        var timer = null;
        return function() {
            timer && clearTimeout(timer);
            var context = this,         // 将执行环境指向当前dom
                arg = arguments;        // 事件e

            if(isImmediate) {
                !timer && fn.call(context, arg);    // timer为null(即没有被执行过,或被重置)
                // 立即执行,后续连续点击不起作用
                timer = setTimeout(function() {
                    timer = null;
                }, delay);
                
            } else {
                timer = setTimeout(function() {
                    fn.call(context, arg);
                },delay);
            }
        }
    }

    var btn = document.querySelector("#submit");
    btn.addEventListener("click", debounce(doAjax, 1000, true));
</script>
demo03.gif

从上图我们可以观察到:第一次点击,请求被执行,后续连续的点击操作都不被执行;等过了1s后,再次点击,请求被执行。

函数节流(throttle)

原理:连续触发某一事件,会固定每隔一段时间执行一次事件回调函数(区别于防抖的连续触发,不执行事件回调函数)
这里,我们以mousemove事件举例:

  • 正常操作
<div id="box"></div>
<script>
    var box = document.getElementById("box");
    var count = 1;
    function doAction() {
        box.innerText = count++;
    }
    box.addEventListener("mousemove", doAction);
</script>

demo04.gif

可以看到,我们的小鼠标,轻轻一划,连续触发了不知道多少次mousemove事件,如果这里涉及到复杂的ajax操作,那又要悲剧了,ε(┬┬﹏┬┬)3!

  • 节流之时间戳实现
<div id="box"></div>
<script>
    var box = document.getElementById("box");
    var count = 1;
    function doAction() {
        box.innerText = count++;
    }
    function throttle(fn, delay) {
        var start = new Date();
        return function() {
            var context = this,         // 将执行环境指向当前dom
                arg = arguments;        // 事件e
            var current = new Date();

            if(current - start >= delay) {
                fn.call(context, arg);
                start = current;
            }
        }
    }


    box.addEventListener("mousemove", throttle(doAction, 1000));
demo05.gif

由上图我们可以观察到:在蓝色块内,连续触发mousemove事件,数字以恒定速率(这里是1s)出现。

  • 节流之定时器实现
<div id="box"></div>
<script>
    var box = document.getElementById("box");
    var count = 1;
    function doAction() {
        box.innerText = count++;
    }
    function throttle(fn, delay) {
        var timer = null;
        return function() {
            var context = this,         // 将执行环境指向当前dom
                arg = arguments;        // 事件e
            if(!timer) {
                timer = setTimeout(function() {
                    timer = null;
                    fn.call(context, arg);
                },delay);
            }
        }
    }

    box.addEventListener("mousemove", throttle(doAction, 1000));

demo06.gif

观察上图,在蓝色块内,连续触发mousemove事件,数字以恒定速率出现。

二者区别如下:

  • 时间戳版会在开始时立即执行一次,最后时间间隔内不再执行;(注册事件函数的时候,会执行一次,拿到初始时间,等你mousemove的时候,时间间隔肯定远大于你的delay时间,或者你不等待,直接触发mousemove事件)
  • 定时器版开始时不执行,最后时间间隔内再执行一次。

总结

防抖与节流的区别

函数防抖好比是公交车停靠在站台后,乘客源源不断地上车,但司机只会等所有乘客上车之后,才发车。
函数节流好比是你每天都会喝水,但是你不会一喝水就上厕所,而是每隔一段时间就去上厕所。

应用场景区别

函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。

参考文献
司徒正美-函数防抖与函数节流
虾扯蛋之函数防抖和节流
前端麻辣烫-JS函数节流与防抖

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 概念解释 在一定时间内,代码执行的次数不一定要非常多,执行的代码越多,带来的效果也是一样,反而会因为执行次数过多而...
    辣瓜瓜阅读 1,521评论 0 2
  • 函数节流 还记得上篇文章中说到的图片懒加载吗?我们在文章的最后实现了一个页面滚动时按需加载图片的方式,即在触发滚动...
    柏丘君阅读 2,822评论 1 19
  •   JavaScript 与 HTML 之间的交互是通过事件实现的。   事件,就是文档或浏览器窗口中发生的一些特...
    霜天晓阅读 3,464评论 1 11
  • 本节介绍各种常见的浏览器事件。 鼠标事件 鼠标事件指与鼠标相关的事件,主要有以下一些。 click 事件,dblc...
    许先生__阅读 2,393评论 0 4
  • 我们是我们的窃窃私语 是夜晚的花园 白瓷器上有细腻的裂纹 就像 一个吻上有细腻的裂纹 我们是我们的床 我们是我们柔...
    我是不是蝎大人阅读 363评论 0 0