摘要:關(guān)于節(jié)流的實現(xiàn),有兩種主流的實現(xiàn)方式,一種是使用時間戳,一種是設(shè)置定時器。當(dāng)觸發(fā)事件的時候,我們設(shè)置一個定時器,再觸發(fā)事件的時候,如果定時器存在,就不執(zhí)行,直到定時器執(zhí)行,然后執(zhí)行函數(shù),清空定時器,這樣就可以設(shè)置下個定時器。
前言JavaScript 專題系列第二篇,講解節(jié)流,帶你從零實現(xiàn)一個 underscore 的 throttle 函數(shù)
在《JavaScript專題之跟著underscore學(xué)防抖》中,我們了解了為什么要限制事件的頻繁觸發(fā),以及如何做限制:
debounce 防抖
throttle 節(jié)流
今天重點講講節(jié)流的實現(xiàn)。
節(jié)流節(jié)流的原理很簡單:
如果你持續(xù)觸發(fā)事件,每隔一段時間,只執(zhí)行一次事件。
根據(jù)首次是否執(zhí)行以及結(jié)束后是否執(zhí)行,效果有所不同,實現(xiàn)的方式也有所不同。
我們用 leading 代表首次是否執(zhí)行,trailing 代表結(jié)束后是否再執(zhí)行一次。
關(guān)于節(jié)流的實現(xiàn),有兩種主流的實現(xiàn)方式,一種是使用時間戳,一種是設(shè)置定時器。
使用時間戳讓我們來看第一種方法:使用時間戳,當(dāng)觸發(fā)事件的時候,我們?nèi)〕霎?dāng)前的時間戳,然后減去之前的時間戳(最一開始值設(shè)為 0 ),如果大于設(shè)置的時間周期,就執(zhí)行函數(shù),然后更新時間戳為當(dāng)前的時間戳,如果小于,就不執(zhí)行。
看了這個表述,是不是感覺已經(jīng)可以寫出代碼了…… 讓我們來寫第一版的代碼:
// 第一版 function throttle(func, wait) { var context, args; var previous = 0; return function() { var now = +new Date(); context = this; args = arguments; if (now - previous > wait) { func.apply(context, args); previous = now; } } }
例子依然是用講 debounce 中的例子,如果你要使用:
container.onmousemove = throttle(getUserAction, 1000);
效果演示如下:
我們可以看到:當(dāng)鼠標(biāo)移入的時候,事件立刻執(zhí)行,每過 1s 會執(zhí)行一次,如果在 4.2s 停止觸發(fā),以后不會再執(zhí)行事件。
使用定時器接下來,我們講講第二種實現(xiàn)方式,使用定時器。
當(dāng)觸發(fā)事件的時候,我們設(shè)置一個定時器,再觸發(fā)事件的時候,如果定時器存在,就不執(zhí)行,直到定時器執(zhí)行,然后執(zhí)行函數(shù),清空定時器,這樣就可以設(shè)置下個定時器。
// 第二版 function throttle(func, wait) { var timeout; var previous = 0; return function() { context = this; args = arguments; if (!timeout) { timeout = setTimeout(function(){ timeout = null; func.apply(context, args) }, wait) } } }
為了讓效果更加明顯,我們設(shè)置 wait 的時間為 3s,效果演示如下:
我們可以看到:當(dāng)鼠標(biāo)移入的時候,事件不會立刻執(zhí)行,晃了 3s 后終于執(zhí)行了一次,此后每 3s 執(zhí)行一次,當(dāng)數(shù)字顯示為 3 的時候,立刻移出鼠標(biāo),相當(dāng)于大約 9.2s 的時候停止觸發(fā),但是依然會在第 12s 的時候執(zhí)行一次事件。
所以比較兩個方法:
第一種事件會立刻執(zhí)行,第二種事件會在 n 秒后第一次執(zhí)行
第一種事件停止觸發(fā)后沒有辦法再執(zhí)行事件,第二種事件停止觸發(fā)后依然會再執(zhí)行一次事件
雙劍合璧那我們想要一個什么樣的呢?
有人就說了:我想要一個有頭有尾的!就是鼠標(biāo)移入能立刻執(zhí)行,停止觸發(fā)的時候還能再執(zhí)行一次!
所以我們綜合兩者的優(yōu)勢,然后雙劍合璧,寫一版代碼:
// 第三版 function throttle(func, wait) { var timeout, context, args, result; var previous = 0; var later = function() { previous = +new Date(); timeout = null; func.apply(context, args) }; var throttled = function() { var now = +new Date(); //下次觸發(fā) func 剩余的時間 var remaining = wait - (now - previous); context = this; args = arguments; // 如果沒有剩余的時間了或者你改了系統(tǒng)時間 if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; func.apply(context, args); } else if (!timeout) { timeout = setTimeout(later, remaining); } }; return throttled; }
效果演示如下:
我們可以看到:鼠標(biāo)移入,事件立刻執(zhí)行,晃了 3s,事件再一次執(zhí)行,當(dāng)數(shù)字變成 3 的時候,也就是 6s 后,我們立刻移出鼠標(biāo),停止觸發(fā)事件,9s 的時候,依然會再執(zhí)行一次事件。
優(yōu)化但是我有時也希望無頭有尾,或者有頭無尾,這個咋辦?
那我們設(shè)置個 options 作為第三個參數(shù),然后根據(jù)傳的值判斷到底哪種效果,我們約定:
leading:false 表示禁用第一次執(zhí)行
trailing: false 表示禁用停止觸發(fā)的回調(diào)
我們來改一下代碼:
// 第四版 function throttle(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : new Date().getTime(); timeout = null; func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function() { var now = new Date().getTime(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } }; return throttled; }取消
在 debounce 的實現(xiàn)中,我們加了一個 cancel 方法,throttle 我們也加個 cancel 方法:
// 第五版 非完整代碼,完整代碼請查看最后的演示代碼鏈接 ... throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = null; } ...注意
我們要注意 underscore 的實現(xiàn)中有這樣一個問題:
那就是 leading:false 和 trailing: false 不能同時設(shè)置。
如果同時設(shè)置的話,比如當(dāng)你將鼠標(biāo)移出的時候,因為 trailing 設(shè)置為 false,停止觸發(fā)的時候不會設(shè)置定時器,所以只要再過了設(shè)置的時間,再移入的話,就會立刻執(zhí)行,就違反了 leading: false,bug 就出來了,所以,這個 throttle 只有三種用法:
container.onmousemove = throttle(getUserAction, 1000); container.onmousemove = throttle(getUserAction, 1000, { leading: false }); container.onmousemove = throttle(getUserAction, 1000, { trailing: false });
至此我們已經(jīng)完整實現(xiàn)了一個 underscore 中的 throttle 函數(shù),恭喜,撒花!
演示代碼相關(guān)的代碼可以在 Github 博客倉庫 中找到
專題系列JavaScript專題系列目錄地址:https://github.com/mqyqingfeng/Blog。
JavaScript專題系列預(yù)計寫二十篇左右,主要研究日常開發(fā)中一些功能點的實現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里、遞歸、亂序、排序等,特點是研(chao)究(xi) underscore 和 jQuery 的實現(xiàn)方式。
如果有錯誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑垊?wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對作者也是一種鼓勵。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/83539.html
摘要:寫在前面專題系列是我寫的第二個系列,第一個系列是深入系列。專題系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點贊,鼓勵指正。 寫在前面 JavaScript 專題系列是我寫的第二個系列,第一個系列是 JavaScript 深入系列。 JavaScript 專題系列共計 20 篇,主要研究日常開發(fā)中一些功能點的實現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里...
摘要:專題系列共計篇,主要研究日常開發(fā)中一些功能點的實現(xiàn),比如防抖節(jié)流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點是研究專題之函數(shù)組合專題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實現(xiàn)模式需求我們需要寫一個函數(shù),輸入,返回。 JavaScript 專題之從零實現(xiàn) jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實現(xiàn)一個 jQuery 的 ext...
摘要:定義定時器清空定時器重置定時器防抖流程觸發(fā)觸發(fā)定義一個定時器,返回執(zhí)行內(nèi)容為清除當(dāng)前定時器,定義執(zhí)行內(nèi)容。 防抖 為了避免一些監(jiān)聽事件為在自己預(yù)料的情況,頻繁觸發(fā)。or 在某些監(jiān)聽命令會頻繁觸發(fā)事件比如resize、mousemove等等 未防抖 示例 var count = 0, Elem = doc.getElementById(con) ...
摘要:定義定時器清空定時器重置定時器防抖流程觸發(fā)觸發(fā)定義一個定時器,返回執(zhí)行內(nèi)容為清除當(dāng)前定時器,定義執(zhí)行內(nèi)容。 防抖 為了避免一些監(jiān)聽事件為在自己預(yù)料的情況,頻繁觸發(fā)。or 在某些監(jiān)聽命令會頻繁觸發(fā)事件比如resize、mousemove等等 未防抖 示例 var count = 0, Elem = doc.getElementById(con) ...
閱讀 2315·2021-11-24 09:39
閱讀 3044·2021-10-15 09:39
閱讀 3097·2021-07-26 23:38
閱讀 2296·2019-08-30 11:14
閱讀 3417·2019-08-29 16:39
閱讀 1718·2019-08-29 15:23
閱讀 785·2019-08-29 13:01
閱讀 2670·2019-08-29 12:29