亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

聊聊lodash的debounce實(shí)現(xiàn)

junfeng777 / 1239人閱讀

摘要:同時(shí),這里會(huì)設(shè)置一個(gè)定時(shí)器,在等待后會(huì)執(zhí)行,的主要作用就是觸發(fā)。最后,如果不再有函數(shù)調(diào)用,就會(huì)在定時(shí)器結(jié)束時(shí)執(zhí)行。問題就出在對(duì)于定時(shí)器的控制上。

本文同步自我的Blog

前段時(shí)間團(tuán)隊(duì)內(nèi)部搞了一個(gè)代碼訓(xùn)練營(yíng),大家組織在一起實(shí)現(xiàn) lodashthrottledebounce,實(shí)現(xiàn)起來覺得并不麻煩,但是最后和官方的一對(duì)比,發(fā)現(xiàn)功能的實(shí)現(xiàn)上還是有差距的,為了尋找我的問題,把官方源碼閱讀了一遍,本文是我閱讀完成后的一篇總結(jié)。

本文只會(huì)列出比較核心部分的代碼和注釋,如果對(duì)全部的源碼有興趣的歡迎直接看我的repo:

什么是throttle和debounce

throttle(又稱節(jié)流)和debounce(又稱防抖)其實(shí)都是函數(shù)調(diào)用頻率的控制器,這里只做簡(jiǎn)單的介紹,如果想了解更多關(guān)于這兩個(gè)定義的細(xì)節(jié)可以看下后文給出的一張圖片,或者閱讀一下lodash的文檔。

throttle:將一個(gè)函數(shù)的調(diào)用頻率限制在一定閾值內(nèi),例如 1s 內(nèi)一個(gè)函數(shù)不能被調(diào)用兩次。

debounce:當(dāng)調(diào)用函數(shù)n秒后,才會(huì)執(zhí)行該動(dòng)作,若在這n秒內(nèi)又調(diào)用該函數(shù)則將取消前一次并重新計(jì)算執(zhí)行時(shí)間,舉個(gè)簡(jiǎn)單的例子,我們要根據(jù)用戶輸入做suggest,每當(dāng)用戶按下鍵盤的時(shí)候都可以取消前一次,并且只關(guān)心最后一次輸入的時(shí)間就行了。

lodash 對(duì)這兩個(gè)函數(shù)又增加了一些參數(shù),主要是以下三個(gè):

leading,函數(shù)在每個(gè)等待時(shí)延的開始被調(diào)用

trailing,函數(shù)在每個(gè)等待時(shí)延的結(jié)束被調(diào)用

maxwait(debounce才有的配置),最大的等待時(shí)間,因?yàn)槿绻?debounce 的函數(shù)調(diào)用時(shí)間不滿足條件,可能永遠(yuǎn)都無法觸發(fā),因此增加了這個(gè)配置,保證大于一段時(shí)間后一定能執(zhí)行一次函數(shù)

這里直接劇透一下,其實(shí) throttle 就是設(shè)置了 maxwaitdebounce,所以我這里也只會(huì)介紹 debounce 的代碼,聰明的讀者們可以自己思考一下為什么。

我的實(shí)現(xiàn)與lodash的區(qū)別

我自己的代碼實(shí)現(xiàn)放在我的repo里,大家有興趣的可以看下。之前說過我的實(shí)現(xiàn)和 lodash 有些區(qū)別,下面就用兩張圖來展示一下。

這是我的實(shí)現(xiàn)

這是lodash的實(shí)現(xiàn)

這里看到,我的代碼主要有兩個(gè)問題:

throttle 的最后一次函數(shù)會(huì)執(zhí)行兩次,而且并非穩(wěn)定復(fù)現(xiàn)。

throttle 里函數(shù)執(zhí)行的順序不對(duì),雖然我的功能實(shí)現(xiàn)了,但是對(duì)于每一次 wait 來說,我都是執(zhí)行的 leading 那一次

lodash 的實(shí)現(xiàn)解讀

下面,我就會(huì)帶著這幾個(gè)問題去看看 lodasah 的代碼。

官方代碼的實(shí)現(xiàn)也不是很復(fù)雜,這里我貼出一些核心部分代碼和我閱讀后的注釋,后面會(huì)講一下 lodash 的大概流程:

function debounce(func, wait, options) {
    let lastArgs,
        lastThis,
        maxWait,
        result,
        timerId,
        lastCallTime

    // 參數(shù)初始化
    let lastInvokeTime = 0 // func 上一次執(zhí)行的時(shí)間
    let leading = false
    let maxing = false
    let trailing = true

    // 基本的類型判斷和處理
    if (typeof func != "function") {
        throw new TypeError("Expected a function")
    }
    wait = +wait || 0
    if (isObject(options)) {
        // 對(duì)配置的一些初始化
    }

    function invokeFunc(time) {
        const args = lastArgs
        const thisArg = lastThis

        lastArgs = lastThis = undefined
        lastInvokeTime = time
        result = func.apply(thisArg, args)
        return result
    }

    function leadingEdge(time) {
        // Reset any `maxWait` timer.
        lastInvokeTime = time
        // 為 trailing edge 觸發(fā)函數(shù)調(diào)用設(shè)定定時(shí)器
        timerId = setTimeout(timerExpired, wait)
        // leading = true 執(zhí)行函數(shù)
        return leading ? invokeFunc(time) : result
    }

   function remainingWait(time) {
        const timeSinceLastCall = time - lastCallTime // 距離上次debounced函數(shù)被調(diào)用的時(shí)間
        const timeSinceLastInvoke = time - lastInvokeTime // 距離上次函數(shù)被執(zhí)行的時(shí)間
        const timeWaiting = wait - timeSinceLastCall // 用 wait 減去 timeSinceLastCall 計(jì)算出下一次trailing的位置

        // 兩種情況
        // 有maxing:比較出下一次maxing和下一次trailing的最小值,作為下一次函數(shù)要執(zhí)行的時(shí)間
        // 無maxing:在下一次trailing時(shí)執(zhí)行 timerExpired
        return maxing
            ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
            : timeWaiting
    }

    // 根據(jù)時(shí)間判斷 func 能否被執(zhí)行
    function shouldInvoke(time) {
        const timeSinceLastCall = time - lastCallTime
        const timeSinceLastInvoke = time - lastInvokeTime

        // 幾種滿足條件的情況
        return (lastCallTime === undefined //首次
            || (timeSinceLastCall >= wait) // 距離上次被調(diào)用已經(jīng)超過 wait
            || (timeSinceLastCall < 0) //系統(tǒng)時(shí)間倒退
            || (maxing && timeSinceLastInvoke >= maxWait)) //超過最大等待時(shí)間
    }

    function timerExpired() {
        const time = Date.now()
        // 在 trailing edge 且時(shí)間符合條件時(shí),調(diào)用 trailingEdge函數(shù),否則重啟定時(shí)器
        if (shouldInvoke(time)) {
            return trailingEdge(time)
        }
        // 重啟定時(shí)器,保證下一次時(shí)延的末尾觸發(fā)
        timerId = setTimeout(timerExpired, remainingWait(time))
    }

    function trailingEdge(time) {
        timerId = undefined

        // 有l(wèi)astArgs才執(zhí)行,意味著只有 func 已經(jīng)被 debounced 過一次以后才會(huì)在 trailing edge 執(zhí)行
        if (trailing && lastArgs) {
            return invokeFunc(time)
        }
        // 每次 trailingEdge 都會(huì)清除 lastArgs 和 lastThis,目的是避免最后一次函數(shù)被執(zhí)行了兩次
        // 舉個(gè)例子:最后一次函數(shù)執(zhí)行的時(shí)候,可能恰巧是前一次的 trailing edge,函數(shù)被調(diào)用,而這個(gè)函數(shù)又需要在自己時(shí)延的 trailing edge 觸發(fā),導(dǎo)致觸發(fā)多次
        lastArgs = lastThis = undefined
        return result
    }

    function cancel() {}

    function flush() {}

    function pending() {}

    function debounced(...args) {
        const time = Date.now()
        const isInvoking = shouldInvoke(time) //是否滿足時(shí)間條件

        lastArgs = args
        lastThis = this
        lastCallTime = time  //函數(shù)被調(diào)用的時(shí)間

        if (isInvoking) {
            if (timerId === undefined) { // 無timerId的情況有兩種:1.首次調(diào)用 2.trailingEdge執(zhí)行過函數(shù)
                return leadingEdge(lastCallTime)
            }
            if (maxing) {
                // Handle invocations in a tight loop.
                timerId = setTimeout(timerExpired, wait)
                return invokeFunc(lastCallTime)
            }
        }
        // 負(fù)責(zé)一種case:trailing 為 true 的情況下,在前一個(gè) wait 的 trailingEdge 已經(jīng)執(zhí)行了函數(shù);
        // 而這次函數(shù)被調(diào)用時(shí) shouldInvoke 不滿足條件,因此要設(shè)置定時(shí)器,在本次的 trailingEdge 保證函數(shù)被執(zhí)行
        if (timerId === undefined) {
            timerId = setTimeout(timerExpired, wait)
        }
        return result
    }
    debounced.cancel = cancel
    debounced.flush = flush
    debounced.pending = pending
    return debounced
}

這里我用文字來簡(jiǎn)單描述一下流程:

首次進(jìn)入函數(shù)時(shí)因?yàn)?lastCallTime === undefined 并且 timerId === undefined,所以會(huì)執(zhí)行 leadingEdge,如果此時(shí) leading 為 true 的話,就會(huì)執(zhí)行 func。同時(shí),這里會(huì)設(shè)置一個(gè)定時(shí)器,在等待 wait(s) 后會(huì)執(zhí)行 timerExpired,timerExpired 的主要作用就是觸發(fā) trailing。

如果在還未到 wait 的時(shí)候就再次調(diào)用了函數(shù)的話,會(huì)更新 lastCallTime,并且因?yàn)榇藭r(shí) isInvoking 不滿足條件,所以這次什么也不會(huì)執(zhí)行。

時(shí)間到達(dá) wait 時(shí),就會(huì)執(zhí)行我們一開始設(shè)定的定時(shí)器timerExpired,此時(shí)因?yàn)閠ime-lastCallTime < wait,所以不會(huì)執(zhí)行 trailingEdge。

這時(shí)又會(huì)新增一個(gè)定時(shí)器,下一次執(zhí)行的時(shí)間是 remainingWait,這里會(huì)根據(jù)是否有 maxwait 來作區(qū)分:

如果沒有 maxwait,定時(shí)器的時(shí)間是 wait - timeSinceLastCall,保證下一次 trailing 的執(zhí)行。

如果有 maxing,會(huì)比較出下一次 maxing 和下一次 trailing 的最小值,作為下一次函數(shù)要執(zhí)行的時(shí)間。

最后,如果不再有函數(shù)調(diào)用,就會(huì)在定時(shí)器結(jié)束時(shí)執(zhí)行 trailingEdge。

我的問題出在哪?

那么,回到上面的兩個(gè)問題,我的代碼究竟是哪里出了問題呢?

為什么順序圖不對(duì)

研究了一下,lodash是比較穩(wěn)定的在trailing時(shí)觸發(fā)前一次函數(shù)調(diào)用的,而我的則是每次在 maxWait 時(shí)觸發(fā)的下一次調(diào)用。問題就出在對(duì)于定時(shí)器的控制上。

因?yàn)樵诰幋a時(shí)考慮到定時(shí)器和 maxwait 會(huì)沖突的問題,在函數(shù)每次被調(diào)用的時(shí)候都會(huì) clearTimeout(timer),因此我的 trailing 判斷其實(shí)只對(duì)整個(gè)執(zhí)行流的最后一次有效,而非 lodash 所說的 trailing 控制的是函數(shù)在每個(gè) wait 的最后執(zhí)行。

而 lodash 并不會(huì)清除定時(shí)器,只是每次生成新的定時(shí)器的時(shí)候都會(huì)根據(jù) lastCallTime 來計(jì)算下一次該執(zhí)行的時(shí)間,不僅保證了定時(shí)器的準(zhǔn)確性,也保證了對(duì)每次 trailing 的控制。

為什么最后會(huì)觸發(fā)兩次

通過打 log 我發(fā)現(xiàn)這種觸發(fā)兩次的情況非常湊巧,最后一次函數(shù)執(zhí)行的時(shí)候,正好滿足前一個(gè)時(shí)延的 trailing,然后自己這個(gè) wait 的定時(shí)器也觸發(fā)了,所以最后又觸發(fā)了一次本次時(shí)延的 trailing,所以觸發(fā)了兩次。

理論上 lodash 也會(huì)出現(xiàn)這種情況,但是它在每次函數(shù)執(zhí)行的時(shí)候都會(huì)刪除 lastArgs 和 lastThis,而下次函數(shù)執(zhí)行的時(shí)候都會(huì)判斷這兩個(gè)參數(shù)是否存在,因此避免了這種情況。

總結(jié)

其實(shí)之前就知道 debouncethrottle 的用途和含義,但是每次用起來都得去看一眼文檔,通過這次自己實(shí)現(xiàn)以及對(duì)源碼的閱讀,終于做到了了熟于心,也發(fā)現(xiàn)自己的代碼設(shè)計(jì)能力還是有缺陷,一開始并沒有想的很到位。

寫代碼的,還是要多寫,多看;慢慢做到會(huì)寫,會(huì)看;與大家共勉。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/89922.html

相關(guān)文章

  • 【源碼分析】給你幾個(gè)鬧鐘,或許用 10 分鐘就能寫出 lodash debounce &

    摘要:最簡(jiǎn)單的案例以最簡(jiǎn)單的情景為例在某一時(shí)刻點(diǎn)只調(diào)用一次函數(shù),那么將在時(shí)間后才會(huì)真正觸發(fā)函數(shù)。后續(xù)我們會(huì)逐漸增加黑色鬧鐘出現(xiàn)的復(fù)雜度,不斷去分析紅色鬧鐘的位置。 序 相比網(wǎng)上教程中的 debounce 函數(shù),lodash 中的 debounce 功能更為強(qiáng)大,相應(yīng)的理解起來更為復(fù)雜; 解讀源碼一般都是直接拿官方源碼來解讀,不過這次我們采用另外的方式:從最簡(jiǎn)單的場(chǎng)景開始寫代碼,然后慢慢往源碼...

    余學(xué)文 評(píng)論0 收藏0
  • 函數(shù)防抖(debounce)和節(jié)流(throttle)以及lodashdebounce源碼賞析

    摘要:防抖函數(shù)防抖和節(jié)流是一對(duì)常常被放在一起的場(chǎng)景。同時(shí),這里會(huì)設(shè)置一個(gè)定時(shí)器,在等待后會(huì)執(zhí)行,的主要作用就是觸發(fā)。最后,如果不再有函數(shù)調(diào)用,就會(huì)在定時(shí)器結(jié)束時(shí)執(zhí)行。 函數(shù)節(jié)流和去抖的出現(xiàn)場(chǎng)景,一般都伴隨著客戶端 DOM 的事件監(jiān)聽。比如scroll resize等事件,這些事件在某些場(chǎng)景觸發(fā)非常頻繁。 比如,實(shí)現(xiàn)一個(gè)原生的拖拽功能(不能用 H5 Drag&Drop API),需要一路監(jiān)聽...

    Enlightenment 評(píng)論0 收藏0
  • throttle函數(shù)與debounce函數(shù)

    摘要:當(dāng)函數(shù)被再次觸發(fā)時(shí),清除已設(shè)置的定時(shí)器,重新設(shè)置定時(shí)器。函數(shù)設(shè)置定時(shí)器,并根據(jù)傳參配置決定是否在等待開始時(shí)執(zhí)行函數(shù)。函數(shù)取消定時(shí)器,并重置內(nèi)部參數(shù)。 throttle函數(shù)與debounce函數(shù) 有時(shí)候,我們會(huì)對(duì)一些觸發(fā)頻率較高的事件進(jìn)行監(jiān)聽,如果在回調(diào)里執(zhí)行高性能消耗的操作,反復(fù)觸發(fā)時(shí)會(huì)使得性能消耗提高,瀏覽器卡頓,用戶使用體驗(yàn)差。或者我們需要對(duì)觸發(fā)的事件延遲執(zhí)行回調(diào),此時(shí)可以借助th...

    Prasanta 評(píng)論0 收藏0
  • 【譯】通過例子解釋 Debounce 和 Throttle

    摘要:舉例舉例通過拖拽瀏覽器窗口,可以觸發(fā)很多次事件。不支持,所以不能在服務(wù)端用于文件系統(tǒng)事件。總結(jié)將一系列迅速觸發(fā)的事件例如敲擊鍵盤合并成一個(gè)單獨(dú)的事件。確保一個(gè)持續(xù)的操作流以每毫秒執(zhí)行一次的速度執(zhí)行。 Debounce 和 Throttle 是兩個(gè)很相似但是又不同的技術(shù),都可以控制一個(gè)函數(shù)在一段時(shí)間內(nèi)執(zhí)行的次數(shù)。 當(dāng)我們?cè)诓僮?DOM 事件的時(shí)候,為函數(shù)添加 debounce 或者 th...

    LeoHsiun 評(píng)論0 收藏0
  • 快速 TypeScript 化 lodash throttle & debounce

    摘要:背景需要包寫起來爽,然而如果遇到?jīng)]有現(xiàn)成的化的工具函數(shù),就需要自己想辦法弄出一份類型聲明文件了。最為重要的是,這種遷移方面我們可以隨意自定義化中所需要的工具函數(shù),遷移粒度都可以由自己控制。 1、背景 1.1、需要 TS 包 TypeScript 寫起來爽,然而如果遇到?jīng)]有現(xiàn)成的 TS 化的工具函數(shù),就需要自己想辦法弄出一份類型聲明文件了。 前兩天要寫的小工具庫(Typescript 語...

    lewinlee 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<