JS學(xué)習(xí)筆記之函數(shù)防抖與節(jié)流

在日常開發(fā)中栗竖,我們經(jīng)常能夠碰到以下工作場景:

    • 對提交按鈕進行變態(tài)的點擊壓力測試
    • 輸入框內(nèi)容的實時校驗(譬如驗證用戶名是否已存在)
    • 圖片滾動加載scroll操作
    • 窗口放大縮小resize操作
    • 對某一區(qū)域進行mousemove操作

上述頻繁觸發(fā)事件的操作,如果我們不采取任何操作长赞,勢必會造成極差的用戶體驗。譬如态贤,對提交按鈕連續(xù)點擊發(fā)起請求,會給服務(wù)器帶來壓力醋火;窗口放大縮小悠汽,會連續(xù)觸發(fā)瀏覽器的resize函數(shù),如果涉及到大量的dom操作芥驳,勢必又會引起頁面的回流與重繪柿冲,有可能會讓頁面變得卡頓等等。
如果我們不想頻繁的觸發(fā)某一事件兆旬,這時候就可以考慮用函數(shù)防抖假抄、函數(shù)節(jié)流了。

函數(shù)防抖(debounce)

原理:在規(guī)定的時間t內(nèi),如果連續(xù)觸發(fā)某一事件丽猬,則不會調(diào)用事件回調(diào)函數(shù)慨亲;連續(xù)觸發(fā)某一事件,t時間內(nèi)宝鼓,不再觸發(fā)該事件刑棵,則執(zhí)行事件回調(diào)函數(shù)。
我們以連續(xù)點擊提交按鈕為例:

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

在短時間內(nèi)愚铡,我們對提交按鈕連續(xù)點擊蛉签,可以看到連續(xù)的請求被發(fā)起,我想這個時候后端的同事應(yīng)該會淚流滿面吧沥寥。

  • 使用防抖
<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,         // 將執(zhí)行環(huán)境指向當(dāng)前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

我們可以觀察到碍舍,連續(xù)點擊提交按鈕,并沒有執(zhí)行請求邑雅;隔了1s后片橡,執(zhí)行請求(也就是在這1s內(nèi),沒有點擊提交按鈕)淮野,這樣就很好的解決了我們的煩惱捧书。

  • 防抖之立即執(zhí)行
    上述的防抖函數(shù),已經(jīng)可以解決我們大部分場景下的問題骤星,但有一個需要注意的點:點擊該提交按鈕经瓷,需要等一段時間后,才會調(diào)用函數(shù)洞难。而我們工作當(dāng)中的另外一種需求為:點擊后立即執(zhí)行舆吮,在接下來的連續(xù)觸發(fā)中,不執(zhí)行事件回調(diào)函數(shù)
<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,         // 將執(zhí)行環(huán)境指向當(dāng)前dom
                arg = arguments;        // 事件e

            if(isImmediate) {
                !timer && fn.call(context, arg);    // timer為null(即沒有被執(zhí)行過色冀,或被重置)
                // 立即執(zhí)行潭袱,后續(xù)連續(xù)點擊不起作用
                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

從上圖我們可以觀察到:第一次點擊,請求被執(zhí)行锋恬,后續(xù)連續(xù)的點擊操作都不被執(zhí)行敌卓;等過了1s后,再次點擊伶氢,請求被執(zhí)行。

函數(shù)節(jié)流(throttle)

原理:連續(xù)觸發(fā)某一事件瘪吏,會固定每隔一段時間執(zhí)行一次事件回調(diào)函數(shù)(區(qū)別于防抖的連續(xù)觸發(fā)癣防,不執(zhí)行事件回調(diào)函數(shù))
這里,我們以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

可以看到掌眠,我們的小鼠標蕾盯,輕輕一劃,連續(xù)觸發(fā)了不知道多少次mousemove事件蓝丙,如果這里涉及到復(fù)雜的ajax操作级遭,那又要悲劇了,ε(┬┬﹏┬┬)3渺尘!

  • 節(jié)流之時間戳實現(xiàn)
<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,         // 將執(zhí)行環(huán)境指向當(dāng)前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

由上圖我們可以觀察到:在藍色塊內(nèi)挫鸽,連續(xù)觸發(fā)mousemove事件,數(shù)字以恒定速率(這里是1s)出現(xiàn)鸥跟。

  • 節(jié)流之定時器實現(xiàn)
<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,         // 將執(zhí)行環(huán)境指向當(dāng)前dom
                arg = arguments;        // 事件e
            if(!timer) {
                timer = setTimeout(function() {
                    timer = null;
                    fn.call(context, arg);
                },delay);
            }
        }
    }

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

demo06.gif

觀察上圖丢郊,在藍色塊內(nèi),連續(xù)觸發(fā)mousemove事件医咨,數(shù)字以恒定速率出現(xiàn)枫匾。

二者區(qū)別如下:

  • 時間戳版會在開始時立即執(zhí)行一次,最后時間間隔內(nèi)不再執(zhí)行拟淮;(注冊事件函數(shù)的時候干茉,會執(zhí)行一次,拿到初始時間很泊,等你mousemove的時候角虫,時間間隔肯定遠大于你的delay時間,或者你不等待委造,直接觸發(fā)mousemove事件)
  • 定時器版開始時不執(zhí)行上遥,最后時間間隔內(nèi)再執(zhí)行一次。

總結(jié)

防抖與節(jié)流的區(qū)別

函數(shù)防抖好比是公交車驼浚靠在站臺后粉楚,乘客源源不斷地上車,但司機只會等所有乘客上車之后,才發(fā)車模软。
函數(shù)節(jié)流好比是你每天都會喝水伟骨,但是你不會一喝水就上廁所,而是每隔一段時間就去上廁所燃异。

應(yīng)用場景區(qū)別

函數(shù)節(jié)流不管事件觸發(fā)有多頻繁携狭,都會保證在規(guī)定時間內(nèi)一定會執(zhí)行一次真正的事件處理函數(shù),而函數(shù)防抖只是在最后一次事件后才觸發(fā)一次函數(shù)回俐。 比如在頁面的無限加載場景下逛腿,我們需要用戶在滾動頁面時,每隔一段時間發(fā)一次 Ajax 請求仅颇,而不是在用戶停下滾動頁面操作時才去請求數(shù)據(jù)单默。這樣的場景,就適合用節(jié)流技術(shù)來實現(xiàn)忘瓦。

參考文獻
司徒正美-函數(shù)防抖與函數(shù)節(jié)流
蝦扯蛋之函數(shù)防抖和節(jié)流
前端麻辣燙-JS函數(shù)節(jié)流與防抖

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末搁廓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子耕皮,更是在濱河造成了極大的恐慌境蜕,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凌停,死亡現(xiàn)場離奇詭異粱年,居然都是意外死亡,警方通過查閱死者的電腦和手機罚拟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門逼泣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舟舒,你說我怎么就攤上這事拉庶。” “怎么了秃励?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵氏仗,是天一觀的道長。 經(jīng)常有香客問我夺鲜,道長皆尔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任币励,我火速辦了婚禮慷蠕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘食呻。我一直安慰自己流炕,他們只是感情好澎现,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著每辟,像睡著了一般剑辫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渠欺,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天妹蔽,我揣著相機與錄音,去河邊找鬼挠将。 笑死胳岂,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的舔稀。 我是一名探鬼主播乳丰,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼镶蹋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赏半,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤贺归,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后断箫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拂酣,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年仲义,在試婚紗的時候發(fā)現(xiàn)自己被綠了婶熬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡埃撵,死狀恐怖赵颅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情暂刘,我是刑警寧澤饺谬,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站谣拣,受9級特大地震影響募寨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜森缠,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一拔鹰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贵涵,春花似錦列肢、人聲如沸恰画。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锣尉。三九已至,卻和暖如春决采,著一層夾襖步出監(jiān)牢的瞬間自沧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工树瞭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拇厢,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓晒喷,卻偏偏與公主長得像孝偎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子凉敲,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 概念解釋 在一定時間內(nèi)爷抓,代碼執(zhí)行的次數(shù)不一定要非常多势决,執(zhí)行的代碼越多,帶來的效果也是一樣蓝撇,反而會因為執(zhí)行次數(shù)過多而...
    辣瓜瓜閱讀 1,541評論 0 2
  • 函數(shù)節(jié)流 還記得上篇文章中說到的圖片懶加載嗎果复?我們在文章的最后實現(xiàn)了一個頁面滾動時按需加載圖片的方式,即在觸發(fā)滾動...
    柏丘君閱讀 2,835評論 1 19
  • ??JavaScript 與 HTML 之間的交互是通過事件實現(xiàn)的。 ??事件独柑,就是文檔或瀏覽器窗口中發(fā)生的一些特...
    霜天曉閱讀 3,473評論 1 11
  • 本節(jié)介紹各種常見的瀏覽器事件迈窟。 鼠標事件 鼠標事件指與鼠標相關(guān)的事件,主要有以下一些忌栅。 click 事件菠隆,dblc...
    許先生__閱讀 2,419評論 0 4
  • 我們是我們的竊竊私語 是夜晚的花園 白瓷器上有細膩的裂紋 就像 一個吻上有細膩的裂紋 我們是我們的床 我們是我們?nèi)?..
    我是不是蝎大人閱讀 368評論 0 0