JS專題之節流函數

本文共 2000 字,讀完只需 8 分鐘

上一篇文章講了去抖函數,然后這一篇講同樣為了優化性能,降低事件處理頻率的節流函數。

一、什么是節流?

節流函數(throttle)就是讓事件處理函數(handler)在大于等于執行周期時才能執行,周期之內不執行,即事件一直被觸發,那么事件將會按每小段固定時間一次的頻率執行。

打個比方:王者榮耀、英雄聯盟、植物大戰僵尸游戲中,技能的冷卻時間,技能的冷卻過程中,是無法使用技能的,只能等冷卻時間到之后才能執行。

那什么樣的場景能用到節流函數呢?
比如:

  1. 頁面滾動和改變大小時需要進行業務處理,比如判斷是否滑到底部,然后進行懶加載數據。
  2. 按鈕被高頻率地點擊時,比如游戲和搶購網站。

我們通過一個簡單的示意來理解:

節流函數可以用時間戳和定時器兩種方式進行處理。

二、時間戳方式實現

<div class="container" id="container">正在滑動:0
</div><script>
window.onload = function() {var bodyEl = document.getElementsByTagName("body")[0]
}var count = 0;
window.onmousemove = throttle(eventHandler, 1000);function eventHandler(e) {var containerEl = document.getElementById("container");containerEl.innerHTML = "正在滑動: " + count;count++;
}function throttle(func, delay) {var delay = delay || 1000;var previousDate = new Date();var previous = previousDate.getTime();  // 初始化一個時間,也作為高頻率事件判斷事件間隔的變量,通過閉包進行保存。return function(args) {var context = this;var nowDate = new Date();var now = nowDate.getTime();if (now - previous >= delay) {  // 如果本次觸發和上次觸發的時間間隔超過設定的時間func.call(context, args);  // 就執行事件處理函數 (eventHandler)previous = now;  // 然后將本次的觸發時間,作為下次觸發事件的參考時間。}}
}
</script>

看時間戳實現版本的效果:

三、定時器方式實現

<div class="container" id="container">正在滑動: 0
</div><script>
window.onload = function() {var bodyEl = document.getElementsByTagName("body")[0]
}var count = 0;
window.onmousemove = throttle(eventHandler, 1000);function eventHandler(e) {var containerEl = document.getElementById("container");containerEl.innerHTML = "正在滑動: " + count;count++;
}function throttle(func, delay) {var delay = delay || 1000;var timer = null;return function(args) {var context = this;var nowDate = new Date();var now = nowDate.getTime();if (!timer) {timer = setTimeout(function() {func.call(context, args);timer = null;}, delay)}}}
</script>

看看定時器版實現版本的效果:

三、時間戳和定時器的對比分析

對比時間戳和定時器兩種方式,效果上的區別主要在于:

事件戳方式會立即執行,定時器會在事件觸發后延遲執行,而且事件停止觸發后還會再延遲執行一次。

具體選擇哪種方式取決于使用場景。underscore 把這兩類場景用 leading 和 trailing 進行了表示。

四、兼容兩種方式, underscore 源碼實現

underscore 的源碼中就同時實現了時間戳和定時器實現方式,在調用時可以自由選擇要不要在間隔時間開始時(leading)執行,或是間隔時間結束后(trailing)執行。

具體看偽代碼和示意圖:

<div class="container" id="container">正在滑動: 0</div><div class="height"></div><script>window.onload = function() {var bodyEl = document.getElementsByTagName("body")[0]}var count = 0;// 事件處理函數function eventHandler(e) {var containerEl = document.getElementById("container");containerEl.innerHTML = "正在滑動: " + count;count++;}var _throttle = function(func, wait, options) {var context, args, result;// 定時器變量默認為 null, 是為了如果想要觸發了一次后再延遲執行一次。var timeout = null;// 上一次觸發事件回調的時間戳。 默認為 0 是為了方便第一次觸發默認立即執行var previous = 0;// 如果沒有傳入 options 參數// 則將 options 參數置為空對象if (!options)options = {};var later = function() {// 如果 options.leading === false// 則每次觸發回調后將 previous 置為 0, 表示下次事件觸發會立即執行事件處理函數// 否則置為當前時間戳previous = options.leading === false ? 0 : +new Date();// 剩余時間跑完,執行事件,并把定時器變量置為空,如果不為空,那么剩余時間內是不會執行事件處理函數的,見 else if 那。timeout = null;result = func.apply(context, args);// 剩余時間結束,并執行完事件后,清理閉包中自由變量的內存垃圾,因為不再需要了。if (!timeout)context = args = null;};// 返回的事件回調函數return function() {// 記錄當前時間戳var now = +new Date();// 第一次執行回調(此時 previous 為 0,之后 previous 值為上一次時間戳)// 并且如果程序設定第一個回調不是立即執行的(options.leading === false)// 則將 previous 值(表示上次執行的時間戳)設為 now 的時間戳(第一次觸發時)// 表示剛執行過,這次就不用執行了if (!previous && options.leading === false)previous = now;// 間隔時間 和 上一次到本次事件觸發回調的持續時間的時間差var remaining = wait - (now - previous);context = this;args = arguments;// 如果間隔時間還沒跑完,則不會執行任何事件處理函數。// 如果超過間隔時間,就可以觸發方法(remaining <= 0)// remaining > wait,表示客戶端系統時間被調整過// 也會立即執行 func 函數if (remaining <= 0 || remaining > wait) {if (timeout) {clearTimeout(timeout);// 解除引用,防止內存泄露timeout = null;}// 重置前一次觸發的時間戳previous = now;// result 為事件處理函數(handler)的返回值// 采用 apply 傳遞類數組對象 argumentsresult = func.apply(context, args);// 引用置為空,防止內存泄露if (!timeout)context = args = null;} else if (!timeout && options.trailing !== false) {// 如果 remaining > 0, 表示在間隔時間內,又觸發了一次事件// 如果 trailing 為真,則會在間隔時間結束時執行一次事件處理函數(handler)// 在從觸發到剩余時間跑完,會利用一個定時器執行事件處理函數,并在定時器結束時把 定時器變量置為空// 如果剩余事件內已經存在一個定時器,則不會進入本  else if 分支, 表示剩余時間已經有一個定時器在運行,該定時器會在剩余時間跑完后執行。// 如果 trailing =  false,即不需要在剩余時間跑完執行事件處理函數。// 間隔 remaining milliseconds 后觸發 later 方法timeout = setTimeout(later, remaining);}// 回調返回值return result;};};window.onmousemove = _throttle(eventHandler, 1000);</script>

下面是我畫的示意圖:

大致總結一下代碼對事件處理邏輯的影響:

  1. 如果 leading 為真,那么綠色意味著間隔時間的開始會立即執行,第一次觸發也會立即執行。
  2. 如果 trailing 為真,那么從藍紫色的豎細線后的剩余事件,會跑一個定時器,定時器在時間間隔結束時再執行一次事件處理函數。
  3. 如果 leading 不為真,那么第一次事件觸發不會立即執行。
  4. 如果 trailing 不為真,最后一次事件觸發后,不然再執行一次事件處理函數。

節流和去抖的常見場景

  1. 輸入框打字輸入完后才開始異步請求數據校驗內容:去抖
  2. 下拉滾動條判斷是否到底部,進行懶加載數據:去抖和節流都可以,判斷是否到底的方式不同
  3. 活動網站、游戲網站,按鈕被瘋狂點擊:節流

六、總結

去抖和節流函數都是為了降低高頻率事件觸發的事件處理頻率,從而優化網頁中大量重繪重排帶來的性能問題。

其區別在于去抖會在高頻率事件觸發時,只執行一次,節流會在滿足間隔時間后執行一次。去抖的 immediate,節流中的 leading, trailing 都是為了盡可能滿足這類工具函數的不同使用場景。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/281308.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/281308.shtml
英文地址,請注明出處:http://en.pswp.cn/news/281308.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

vue 2.6 插槽v-slot用法記錄

v-slot用法簡記用法示例匿名插槽與具名插槽插槽作用域組件使用插槽動態命名總結用法示例 vue2.6統一了插槽的語法v-slot 匿名插槽與具名插槽 在其他組件中使用child組件 <child><template v-slot:slotName>hello world</template> </child>child組…

Latex排版全解(轉)

Latex排版全解 http://blog.csdn.net/langb2014/article/details/51354238轉載于:https://www.cnblogs.com/yifdu25/p/8338399.html

git-ftp Can't access remote 'ft://...', exiting...問題記錄

環境 服務器&#xff1a;西部數碼虛擬主機 本地系統&#xff1a;windows 10 (LTSC 2019) 軟件&#xff1a; Git Bash&#xff0c;gti-ftp (版本1.6.0) 問題 在使用git ftp init初始化上傳代碼的時候會出現 $ git ftp init fatal: Cant access remote ftp://dmkt:***dmkt.goto…

【Flutter教程】從零構建電商應用(一)

在這個系列中&#xff0c;我們將學習如何使用google的移動開發框架flutter創建一個電商應用。本文是flutter框架系列教程的第一部分&#xff0c;將學習如何安裝Flutter開發環境并創建第一個Flutter應用&#xff0c;并學習Flutter應用開發中的核心概念&#xff0c;例如widget、狀…

為OWA自定義快捷鍵

這篇短文分享一下如何為自己常用的網頁添加自定義功能&#xff0c;例如添加快捷鍵。我這里用一個常用的網站作為范例。它是Outlook Web Access (OWA), 它的地址一般如下。我在寫郵件時希望能用一些快捷鍵來提高工作效率&#xff0c;但系統默認自帶的快捷鍵特別少&#xff0c;而…

數據結構 快速排序

快速排序是對冒泡排序的一種改進&#xff0c;是所有內部排序算法中平均性能最優的排序算法。其基本思想是基于分治法的&#xff1a;在待排序數組L[1...n]中任取一個元素pivot作為基準&#xff0c;從數組的兩端開始掃描。設兩個指示標志&#xff08;low指向起始位置&#xff0c;…

Finally語句塊的運行

一、finally語句塊是否一定運行&#xff1f; Java中異常捕獲機制try...catch...finally塊中的finally語句是不是一定會被運行&#xff1f;非常多人都說不是。當然他們的回答是正確的&#xff0c;經過試驗。至少下面有兩種情況下finally語句是不會被運行的&#xff1a; &#xf…

vue-cli 3.0 跨域請求代理

官方文檔中指明&#xff0c;跨域請求可以通過配置vue.config.js中的devServer.proxy選項來進行配置。 這個選項配置的本質實際上就是http-proxy-middleware中間件的用法&#xff0c;和Webpack-dev-server的proxy一樣。 vue-cli 3.0中介紹了兩種常見的用法&#xff1a; modul…

小米人員架構調整:組建中國區,王川任總裁

12月13日上午&#xff0c;小米內部發布人員調整公開信&#xff0c;信中傳達了兩個重要內容&#xff1a;將銷售與服務部改組為中國區&#xff0c;任命集團高級副總裁王川兼任中國區總裁。 在今年9月份&#xff0c;也就是小米上市前夕&#xff0c;雷軍在一封內部信中宣布對公司組…

在 .NET 7上使用 WASM 和 WASI

WebAssembly&#xff08;WASM&#xff09;和WebAssembly System Interface&#xff08;WASI&#xff09;為開發人員開辟了新的世界。.NET 開發人員在 Blazor WebAssembly 發布時熟悉了 WASM。Blazor WebAssembly 在瀏覽器中基于 WebAssembly 的 .NET 運行時上運行客戶端。WASI通…

Java基礎 五 方法

方法 1.1 方法概述 在我們的日常生活中&#xff0c;方法可以理解為要做某件事情&#xff0c;而采取的解決辦法。 如&#xff1a;小明同學在路邊準備坐車來學校學習。這就面臨著一件事情&#xff08;坐車到學校這件事情&#xff09;需要解決&#xff0c;解決辦法呢&#xf…

django rest framework 過濾 lim分頁

一.過濾 1.首先引用diango 自帶的過濾配置 2.導入模塊 from django_filters.rest_framework import DjangoFilterBackend from django_filters import rest_framework as filters 3.一種簡單的過濾: class BookView(ModelViewSet):queryset Book.objects.all()serializer_clas…

MySQL用戶及權限管理

MySQL用戶及權限管理查看用戶及權限查看用戶及作用域&#xff08;使用范圍&#xff09;查看用戶權限創建用戶及授權字段參數用戶管理使用命令提示符登錄MySQL mysql -h localhost -u root -p查看用戶及權限 mysql中的用戶信息和權限等都存儲在一個名為mysql的數據庫中。其中主…

附近有什么?8款可以查周邊的App

如今科技發達的時代&#xff0c;手機的功能不僅僅只是能通訊聊天&#xff0c;而是逐漸的走進了人們的生活中。因為有了APP&#xff0c;我們的生活才更豐富&#xff0c;并且有很多是我們生活中不可缺少的軟件&#xff0c;而這些軟件便是根據手機中的GPS定位系統而來的。簡單來說…

MyEclipse小問題與漢字處理

今天在使用MyEclipse時&#xff0c;遇到工作目錄報錯(如上圖)&#xff0c;解決方法如下&#xff1a;找到對應工作區(查看工作區的方法為&#xff1a;單擊File → Switch Workspace 即可)依次打開 .metadata文件夾 → .plugins文件夾 → org.eclipse.core.runtime文件夾 → .set…

關系數據庫設計及優化原則

一直以來就想總結一下自己這么多年來在關系數據庫上積累的經驗。奈何自己是一個比較懶的人一直不想動手去寫。扎克伯格曾說過&#xff1a;“想做一件事的話&#xff0c;最好的辦法就是先開始”。索性就先寫一點東西&#xff0c;這些東西不會太長&#xff0c;自然也不會包括太多…

java B2B2C springmvc mybatis電子商務平臺源碼-消息隊列之RocketMQ

RocketMQ出自阿里公司的開源產品&#xff0c;用 Java 語言實現&#xff0c;在設計時參考了 Kafka&#xff0c;并做出了自己的一些改進&#xff0c;消息可靠性上比 Kafka 更好。RocketMQ在阿里集團被廣泛應用在訂單&#xff0c;交易&#xff0c;充值&#xff0c;流計算&#xff…

VSCode同步設置

2022/4/1 更新 剛剛發現還有人在看這篇文章&#xff0c;這里更新一下&#xff0c;VSCode 從1.48版本開始已經內置了同步功能&#xff0c;可以不用再使用Settings Sync插件了。 點擊左下角的用戶或者設置的 Sign in to Sync Setting&#xff0c;使用GitHub或者Microsoft賬戶登…

配置三臺服務器組成的ELK集群(二)

上一篇里主要是介紹了ES和ES-Head的安裝過程&#xff0c;這一篇繼續介紹ELK集群的其他核心組件安裝過程。 五、安裝Logstash&#xff1a; 本案的Logstash安裝在10.113.130.117上&#xff1b;燃鵝&#xff0c;Logstash也可以利用多臺組成集群&#xff0c;如果未來單臺處理不過來…

Discuz X3.2源碼解析 discuz_application類(轉自百度)

discuz_application在/source/class/discuz/discuz_application.php中。 discuz_application繼承自抽象類discuz_base discuz_application主要實現對運行環境、配置、輸入、輸出、數據庫、設置、用戶、session、移動模塊、計劃任務、手機預覽等方面的初始化。 instance()函數來…