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

資訊專欄INFORMATION COLUMN

由淺入深學(xué)習(xí)lodash的debounce函數(shù)

Raaabbit / 919人閱讀

摘要:定時(shí)器調(diào)用頻率優(yōu)化把開啟定時(shí)器的邏輯放在可以大大減少定時(shí)器的數(shù)量。舉個(gè)例子,比如為,此時(shí)在某一個(gè)定時(shí)器的回調(diào)函數(shù)檢測到上一次觸法事件的為,而為,此時(shí)雖然要開啟下一次定時(shí),但這個(gè)時(shí)候定時(shí)的時(shí)間為就可以了。

最近的面試中考到了debounce,函數(shù)防抖,筆試的時(shí)候答的不是特別好,下來好好研究了一下,從原理到優(yōu)化,再到開源工具庫lodash的實(shí)現(xiàn)源碼,梳理了一番,現(xiàn)整理如下。

先簡單介紹一下debounce,從最簡單的一個(gè)場景入手,當(dāng)用戶不斷點(diǎn)擊頁面,短時(shí)間內(nèi)頻繁的觸法點(diǎn)擊事件,只有在用戶觸法事件后的ns時(shí)間內(nèi),沒有再觸法事件,真正的監(jiān)聽函數(shù)才會執(zhí)行,如果在這段時(shí)間內(nèi)再次觸法了事件,就需要重新計(jì)算這個(gè)ns。

debounce最主要的作用是把多個(gè)觸法事件的操作延遲到最后一次觸法執(zhí)行,在性能上做了一定的優(yōu)化。

不使用debounce

如果不使用debounce,那就會每一次點(diǎn)擊都會觸法事件的回調(diào)函數(shù),這有時(shí)候?qū)τ谛阅苁且环N巨大的浪費(fèi)(比如大量的增加dom元素)?;蛘弋?dāng)回調(diào)函數(shù)計(jì)算量很大的時(shí)候,甚至?xí)?dǎo)致阻塞。

window.addEventListener("click", function (event) {
  var p = document.createElement("p")
  p.innerHTML = "trigger"
  document.body.appendChild(p)
})

頻繁觸法
可以看出,每一次點(diǎn)擊都會觸法函數(shù)執(zhí)行。

使用debounce
window.addEventListener("click", debounce(function (event) {
    var p = document.createElement("p")
    p.innerHTML = "trigger"
    document.body.appendChild(p)
    return "aaaa"
}, 500))

debounce優(yōu)化
可以看出,只有在最后一次點(diǎn)擊的500ms后,真正的函數(shù)func才會觸法。

開始實(shí)現(xiàn)debounce

本篇文章的debounce實(shí)現(xiàn)主要參考了lodash庫,會從最基礎(chǔ)的實(shí)現(xiàn)開始,一步步完善它。
debounce的核心實(shí)現(xiàn),就是要判斷每次觸法事件的時(shí)候,要不要執(zhí)行真正的func。

大體思路就是每次觸法事件都開啟一個(gè)延時(shí)的定時(shí)器,在定時(shí)器結(jié)束的時(shí)候?qū)Ρ扰c最后一次觸法事件時(shí)的時(shí)間差,如果時(shí)間差大于延遲的閾值,那么就執(zhí)行真正的func`。

大致的結(jié)構(gòu)如下

function debounce (func, wait) {
    var lastCallTime   // 最后一次觸法事件的時(shí)間
    var lastThis       // 作用域
    var lastArgs       // 參數(shù)
    var timerId        // 定時(shí)器對象
    wait = +wait || 0
    // 啟動定時(shí)器
    function startTimer (timerExpired, wait) {
        return setTimeout(timerExpired, wait)
    }
    
    // func函數(shù)執(zhí)行   
    function invokeFunc () {
    
    }
    
    // 調(diào)用func函數(shù)的判定條件 
    function shouldInvoke () {
    
    }
    
    //  定時(shí)器的回調(diào)函數(shù) 
    function timerExpired () {
        // 在這里判斷觸法事件的時(shí)間差
    }
    
    // 要返回的函數(shù)
    function debounced (...args) {
    
    }
    
    return debounced
}

這就是基本的debounce函數(shù)的構(gòu)成,下面邊解析,邊去一一填充這些函數(shù),最后再對函數(shù)進(jìn)行一步步的優(yōu)化。

debounced

每一次觸法事件的時(shí)候都會進(jìn)入到這個(gè)函數(shù),這個(gè)函數(shù)需要做這么幾個(gè)事情。

確定作用域和參數(shù)

更新觸法事件的時(shí)間,也就是lastCallTime

啟動定時(shí)器 timerId

function debounced (...args) {
    const time = Date.now()
    lastThis = this
    lastArgs = args
    lastCallTime = time
    timerId = startTimer(timerExpired, wait)
}
startTimer

startTimer 就是啟動一個(gè)定時(shí)器,后續(xù)會有更多的拓展,所以封裝一個(gè)函數(shù)

function startTimer (timerExpired, wait) {
    return setTimeout(timerExpired, wait)
}
timerExpired

timerExpired 主要判斷是否執(zhí)行func

function timerExpired () {
    const time = Date.now()
    if (shouldInvoke(time)) {
        return invokeFunc()
    }
}
shouldInvoke

shouldInvoke判斷每次事件觸法的時(shí)間差,如果大于閾值,那么真正的func就會執(zhí)行

function shouldInvoke (time) {
    return lastCallTime !== undefined && (time - lastCallTime >= wait)
}
invokeFunc
function invokeFunc () {
    timerId = undefined
    const args = lastArgs
    const thisArg = lastThis
    let result = func.apply(thisArg, args)
    lastArgs = lastThis = undefined
    return result
}

這樣,這個(gè)函數(shù)就寫完了。把每一步拆解開來,理解還是相對容易的,再總結(jié)一下。每一次觸法事件,都開啟一個(gè)定時(shí)器timerId,并且會更新觸法事件的最后時(shí)間lastCallTime,在定時(shí)器的回調(diào)函數(shù)里面,判斷回調(diào)函數(shù)的執(zhí)行時(shí)間與lastCallTime的時(shí)間差,如果大于閾值,說明延遲時(shí)間到了,func執(zhí)行,如果小于,就忽略。

優(yōu)化

雖然實(shí)現(xiàn)了基本的debounce,但在擴(kuò)展它的功能之前,看一看有沒有優(yōu)化的空間,每一次觸法事件都開啟一個(gè)定時(shí)器是不是太浪費(fèi)了。這里可不可以減少調(diào)用次數(shù)。

定時(shí)器調(diào)用頻率優(yōu)化

把開啟定時(shí)器的邏輯放在timerExpired可以大大減少定時(shí)器的數(shù)量。debounced開啟了第一次定時(shí)器后,debounced會忽略后面的定時(shí)器開啟,直到func執(zhí)行之后(timerIdundefined),而在timerExpired里面判斷如果func不滿足觸發(fā)條件,那么就開啟下一個(gè)定時(shí)器。

其實(shí)本質(zhì)就是確保上一個(gè)定時(shí)器的回調(diào)不會觸法func了,才會開啟下一個(gè)定時(shí)器。

優(yōu)化代碼如下

function timerExpired () {
    const time = Date.now()
    if (shouldInvoke(time)) {
        return invokeFunc()
    }
    timerId = startTimer(timerExpired, wait)
}
function debounced (...args) {
    const time = Date.now()
    lastThis = this
    lastArgs = args
    lastCallTime = time
    if (timerId === undefined) {
        timerId = startTimer(timerExpired, wait)
    }
}
定時(shí)器時(shí)間的優(yōu)化

timerExpired 中開啟的定時(shí)器

timerId = startTimer(timerExpired, wait)

延遲的時(shí)間是否一定為wait呢,這是不一定的。
舉個(gè)例子,比如wait5,此時(shí)在某一個(gè)定時(shí)器的回調(diào)函數(shù)timerExpired檢測到上一次觸法事件的lastCallTime100,而Date.now()103,此時(shí)雖然103-100 = 3 < 5,要開啟下一次定時(shí),但這個(gè)時(shí)候定時(shí)的時(shí)間為 5 - 3 = 2就可以了。這才是精確的時(shí)間。

所以我們需要把這個(gè)時(shí)間封裝成一個(gè)函數(shù)remainingWait

function remainingWait(time) {
    const timeSinceLastCall = time - lastCallTime
    const timeWaiting = wait - timeSinceLastCall
    return timeWaiting
}
function timerExpired () {
    const time = Date.now()
    if (shouldInvoke(time)) {
        return invokeFunc()
    }
    timerId = startTimer(timerExpired, remainingWait(time))
}

附上執(zhí)行的流程圖

總結(jié)

這其實(shí)只是實(shí)現(xiàn)了一個(gè)basicDebounce,其實(shí)有的時(shí)候我們需要在頻繁觸法事件的開始立即執(zhí)行func,而忽略后面的觸法事件,這就需要加入?yún)?shù)控制,也就是lodash中的trailingleading,甚至兩者同時(shí)存在,頭尾各執(zhí)行一次,還有就是throttle函數(shù)節(jié)流,保證在一段時(shí)間內(nèi)func至少執(zhí)行一次,這就是lodash中的maxWait參數(shù)。下一篇文章會完善這些功能,屆時(shí),一個(gè)完整的debounce才是真正的實(shí)現(xiàn)了。

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

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

相關(guān)文章

  • lodash源碼學(xué)習(xí)節(jié)流與防抖

    摘要:首先重置防抖函數(shù)最后調(diào)用時(shí)間,然后去觸發(fā)一個(gè)定時(shí)器,保證后接下來的執(zhí)行。這就避免了手動管理定時(shí)器。 ??之前遇到過一個(gè)場景,頁面上有幾個(gè)d3.js繪制的圖形。如果調(diào)整瀏覽器可視區(qū)大小,會引發(fā)圖形重繪。當(dāng)圖中的節(jié)點(diǎn)比較多的時(shí)候,頁面會顯得異??D。為了限制類似于這種短時(shí)間內(nèi)高頻率觸發(fā)的情況,我們可以使用防抖函數(shù)。 ??實(shí)際開發(fā)過程中,這樣的情況其實(shí)很多,比如: 頁面的scroll事件 ...

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

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

    余學(xué)文 評論0 收藏0
  • [譯]通過實(shí)例講解Debouncing和Throtting(防抖與節(jié)流)

    摘要:譯通過實(shí)例講解和防抖與節(jié)流源碼中推薦的文章,為了學(xué)習(xí)英語,翻譯了一下原文鏈接作者本文來自一位倫敦前端工程師的技術(shù)投稿。首次或立即你可能發(fā)現(xiàn)防抖事件在等待觸發(fā)事件執(zhí)行,直到事件都結(jié)束后它才執(zhí)行。 [譯]通過實(shí)例講解Debouncing和Throtting(防抖與節(jié)流) lodash源碼中推薦的文章,為了學(xué)習(xí)(英語),翻譯了一下~ 原文鏈接 作者:DAVID CORBACHO 本文來自一位...

    Jenny_Tong 評論0 收藏0
  • JS throttle與debounce區(qū)別

    摘要:可以看下面的栗子這個(gè)圖中圖中每個(gè)小格大約,右邊有原生事件與節(jié)流去抖插件的與事件。即如果有連續(xù)不斷的觸發(fā),每執(zhí)行一次,用在每隔一定間隔執(zhí)行回調(diào)的場景。執(zhí)行啦打印執(zhí)行啦打印執(zhí)行啦節(jié)流按照上面的說明,節(jié)流就是連續(xù)多次內(nèi)的操作按照指定的間隔來執(zhí)行。 一般在項(xiàng)目中我們會對input、scroll、resize等事件進(jìn)行節(jié)流控制,防止事件過多觸發(fā),減少資源消耗;在vue的官網(wǎng)的例子中就有關(guān)于lod...

    wawor4827 評論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 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<