js 防抖、節(jié)流

一. 概念

防抖:顧名思義,防止抖動,指連續(xù)調(diào)用時,只有最后一次生效。
節(jié)流:節(jié)省流量,固定時間間隔觸發(fā)一次,減小頻率。

二. 舉例:

  1. 搜索功能,在用戶輸入結(jié)束以后才開始發(fā)送搜索請求,可以使用函數(shù)防抖來實現(xiàn);
  2. 驗證賬號密碼功能,在用戶輸入密碼結(jié)束以后才開始發(fā)送驗證請求,可以使用函數(shù)防抖來實現(xiàn);

簡單來說:某件事你并不想它太過頻繁觸發(fā),那么設(shè)置一個定時器,每次進來的時候都清除原本的定時器,然后重新開始計時。

  1. 你不想頻繁為你女票買單,于是約好每月清空購物車1次
  2. 防止過快拖動滾動條,導致 JS 跟不上拖動頻率,通過節(jié)流限制每秒觸發(fā)監(jiān)聽的次數(shù)(固定時間固定頻率)

簡單來說:年輕人要保持一日三餐,規(guī)律作息,那就通過節(jié)流來限制。

三. 手寫防抖、節(jié)流思路

1. 首先函數(shù)需要哪些參數(shù)?

  • 都需要傳遞一個函數(shù)進去,返回一個防抖、節(jié)流后的新函數(shù),因此第一個參數(shù)是需要處理的原函數(shù)fn
  • 光有原函數(shù)還不行,因為我們需要規(guī)定一個時間間隔。對于防抖而言:時間間隔用于表明最后一次調(diào)用時,隔多久沒有再次觸發(fā),我們才真正調(diào)用函數(shù)。譬如,放鹽時,手一直抖動,直到連續(xù)5秒沒有抖動了,那么才放鹽。這個5秒就是時間間隔。對于節(jié)流而言:時間間隔用于標志,每隔多久我們真正觸發(fā)函數(shù)調(diào)用。就像是,一天只吃3頓,那么只有每頓間隔4個小時,我們才能吃飯。在沒到4個小時時,不允許吃東西。所以,第二個參數(shù)是時間間隔,可寫為wait

現(xiàn)在,我們可以寫出防抖和節(jié)流函數(shù)的基本結(jié)構(gòu)和參數(shù):

// 防抖
function debounce(fn, wait=500) {
    return function () {
    }
}
 
// 節(jié)流
function throttled(fn, wait=500) {
    return function () {
    }
}

2. 原函數(shù)的執(zhí)行條件是什么?

  • 對于防抖,當連續(xù)觸發(fā)時,我們的目標是,不抖動時才調(diào)用。這也就是說,不滿足時間間隔時,后一次觸發(fā)會覆蓋掉前一次觸發(fā)。當滿足時間間隔時,才會真正執(zhí)行。
    滿足時間間隔,指的是某一次觸發(fā)之后,時間間隔范圍內(nèi),沒有下一次觸發(fā),這樣不產(chǎn)生覆蓋,因此會真正執(zhí)行。由于有時間間隔的判斷,因此即便真正執(zhí)行,也需要延遲到時間間隔之后。這顯然是定時器的概念。

  • 對于節(jié)流,當連續(xù)觸發(fā)時,我們的目標是,每隔時間間隔時調(diào)用一次。這也就是說,我們每次設(shè)定固定時間的一個定時器,當定時器存在就什么都不做,當定時器不存在,就設(shè)定下一次的定時器。

綜上,我們可以大致寫出函數(shù)的執(zhí)行時機,結(jié)構(gòu)如下:

// 防抖
 
function debounce(func, wait=500) {
    let timeout;
    return function () {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
         // 原函數(shù)執(zhí)行部分
        }, wait);
    }
}
 
 
// 節(jié)流
 
function throttle(fn, wait = 500) {
    let timer = null
    return function () {
        if (!timer) {
            timer = setTimeout(() => {
              // 原函數(shù)執(zhí)行部分
            }, wait);
        }
    }
}

3. 原函數(shù)執(zhí)行

我們只需要直接調(diào)用fn,即fn()即可。但這樣不夠完善,需要注意兩點:

  • 其一是this指向,新函數(shù)中調(diào)用原函數(shù),兩者的this指向應該一致;
  • 其二是參數(shù),新函數(shù)中調(diào)用原函數(shù),兩者接收的參數(shù)應該一致。

故而,我們應該努力讓新舊函數(shù)在這兩方面保持一致。

// 防抖
function debounce(func, wait=500) {
    let timeout;
    return function (...args) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
            fn.apply(this, args)
        }, wait);
    }
}
 
// 節(jié)流
function throttle(fn, wait=500) {
    let timer = null
    return function (...args) {
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(this, args)
            }, wait);
        }
    }
}

注1:setTimeout回調(diào)中使用arguments:
若不是箭頭函數(shù),接收的是回調(diào)的實際參數(shù),而不是新函數(shù)的實際參數(shù)。
若是箭頭函數(shù),由于箭頭函數(shù)沒有arguments對象,接收的就是新函數(shù)的實際參數(shù)。
注2:setTimeout回調(diào)中使用this:
若是箭頭函數(shù),由于箭頭函數(shù)沒有this,因此回調(diào)中的this是外層的this,
若不是箭頭函數(shù),回調(diào)中的this指向window。

4. 計時器何時重置?

在上面的代碼中,防抖和節(jié)流何時進行計時器的重置呢?

  • 對于防抖:每次調(diào)用都會清除掉上一次的定時器,因此每次觸發(fā)都會重置,可以看到clearTimeout寫在定時器之前。
  • 對于節(jié)流:一開始沒有定時器,觸發(fā)事件后檢測定時器是否存在,如果不存在則開啟定時器執(zhí)行函數(shù),執(zhí)行完函數(shù)后,在把定時器設(shè)為無,準備下一次開啟定時器執(zhí)行函數(shù)

故針對節(jié)流函數(shù),增加清除操作,即增加timer=null重置語句:

// 防抖
function debounce(func, wait=500) {
    let timeout;
    return function (...args) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
            func.apply(this, args)
        }, wait);
    }
}
 
// 節(jié)流
function throttle(fn, wait = 500) {
    let timer = null
    return function (...args) {
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(this, args);
                timer = null;
            },wait);
        }
    }
}

詳情參考

完整的防抖用例

<body>
    <button id="debounce">點我防抖!</button>
<script>
    // 防抖
    function debounce(func, wait = 500) {
        let timeout = null;
        return function (...args) {
            clearTimeout(timeout)
            timeout = setTimeout(() => {
                func.apply(this, args)
            }, wait);
        }
    }
    function sayDebounce() {
        console.log(this);
        console.log("防抖成功!");
    }

    window.onload = function () {
        var myDebounce = document.getElementById("debounce");
        myDebounce.addEventListener("click", debounce(sayDebounce));
    }
</script>
</body>

完整的節(jié)流用例

<body>
    <button id="throttle">點我節(jié)流!</button>
<script>
    // 節(jié)流
    function throttle(fn, wait = 500) {
        let timer = null
        return function (...args) {
            if (!timer) {
                timer = setTimeout(() => {
                    fn.apply(this, args);
                    timer = null;
                }, wait);
            }
        }
    }
    function sayThrottle() {
      console.log("節(jié)流成功!");
    }
    window.onload = function () {
        var myThrottle = document.getElementById("throttle");
        myThrottle.addEventListener("click", throttle(sayThrottle));
    }
</script>
</body>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 在進行窗口的resize、scroll,輸入框內(nèi)容校驗等操作時,如果事件處理函數(shù)調(diào)用的頻率無限制,會加重瀏覽器的負...
    iqing2012閱讀 891評論 0 1
  • 在上周的開發(fā)中,又遇到點擊保存多次請求數(shù)據(jù)重復的問題,所以下來學習了一下js的防抖和節(jié)流。通過學習了解到,在進行窗...
    any_5637閱讀 429評論 0 2
  • 參考博客:JS防抖和節(jié)流,感謝作者的用心分享日常開發(fā)過程中,滾動事件做復雜計算頻繁調(diào)用回調(diào)函數(shù)很可能會造成頁面的卡...
    上海_前端_求內(nèi)推閱讀 530評論 0 4
  • 防抖和節(jié)流 相同:在不影響客戶體驗的前提下,將頻繁的回調(diào)函數(shù),進行次數(shù)縮減.避免大量計算導致的頁面卡頓.不同:防抖...
    CodeMT閱讀 405評論 0 2
  • 在進行窗口的resize、scroll,輸入框內(nèi)容校驗等操作時,如果事件處理函數(shù)調(diào)用的頻率無限制,會加重瀏覽器的負...
    為光pig閱讀 414評論 0 3

友情鏈接更多精彩內(nèi)容