JavaScript 防抖和節(jié)流詳解
防抖
自動門感應到有人,打開門,并且開始5秒倒計時,在 5 s 內有另外一個人靠近到門,門感應到人,重新5秒倒計時
當事件被觸發(fā)時,設定一個延遲,若期間事件又被觸發(fā),則重新設定延遲,直到延遲結束,執(zhí)行動作 (防止多次觸發(fā))
web 應用上面
- 改變頁面大小的統(tǒng)計
- 滾動頁面位置的統(tǒng)計
- 輸入框連續(xù)輸入的請求次數控制
一開始,點擊按鈕,console.log('pay money')
<body> <button id="btn">click</button> </body> <script> const btn = document.getElementById('btn') function payMoney() { console.log('pay money'); } btn.addEventListener('click', payMoney) </script>
定義 debounce
const btn = document.getElementById('btn') function payMoney() { console.log('pay money'); } function debounce(func) { // 在函數里面返回函數 , 只有當點擊的時候才返回該函數 return function () { func() } } btn.addEventListener('click', debounce(payMoney))
設置延時
const btn = document.getElementById('btn') function payMoney() { console.log('pay money'); } function debounce(func, delay) { return function () { setTimeout(_ => func(), delay) } } btn.addEventListener('click', debounce(payMoney, 1000))
清除延時:未能執(zhí)行
原因
每次點擊的時候就會執(zhí)行返回函數里面的內容
每次點擊的執(zhí)行函數都是獨立的,互不干涉
正因為他們之間沒有聯系,清除延時在這里完全沒有起作用
要讓這些獨立的執(zhí)行函數之間有聯系,需要用到作用域鏈(閉包)
const btn = document.getElementById('btn') function payMoney() { console.log('pay money'); } function debounce(func, delay) { return function () { let timer clearInterval(timer) timer = setTimeout(_ => func(), delay) } } btn.addEventListener('click', debounce(payMoney, 1000))
將 timer 放在返回函數的外圍,這樣在定義監(jiān)聽事件的同時,就定義了 timer
變量
因為作用域鏈,所有獨立的執(zhí)行函數都能訪問到這個timer變量
而且現在這個timer變量只創(chuàng)建了一次。是唯一的,我們只不過不斷給timer賦值進行延時而已
每個清除延時就是清除上一個定義的延時,相當于多個函數共用同一個外部變量
const btn = document.getElementById('btn') function payMoney() { console.log('pay money'); } function debounce(func, delay) { let timer return function () { clearInterval(timer) timer = setTimeout(_ => func(), delay) } } btn.addEventListener('click', debounce(payMoney, 1000))
此時的代碼,this 是指向 window ?
因為回調的原因,運行時已經在Window下了
const btn = document.getElementById('btn') function payMoney() { console.log('pay money'); console.log(this); } function debounce(func, delay) { let timer return function () { clearInterval(timer) timer = setTimeout(_ => func(), delay) } } btn.addEventListener('click', debounce(payMoney, 1000))
解決辦法
在 setTimeout 之前將 this 保存下來,此時的 this 是指向按鈕的
const btn = document.getElementById('btn') function payMoney() { console.log('pay money'); console.log(this); } function debounce(func, delay) { let timer // 只有當點擊的時候,才返回此函數,所以 this 是指向按鈕的 return function () { let context = this clearInterval(timer) timer = setTimeout(_ => { func.apply(context) }, delay) } } btn.addEventListener('click', debounce(payMoney, 1000))
考慮參數的問題,添加 arg
const btn = document.getElementById('btn') function payMoney() { console.log('pay money'); console.log(this); } function debounce(func, delay) { let timer return function () { let context = this let args = arguments clearInterval(timer) timer = setTimeout(_ => { func.apply(context) console.log(context, args); }, delay) } } btn.addEventListener('click', debounce(payMoney, 1000))
節(jié)流
先觸發(fā)一次后,防止接下來多次觸發(fā)
滾動屏幕:統(tǒng)計用戶滾動屏幕的行為來作出相應的網頁反應
當用戶不斷進行滾動,就會不斷產生請求,相應也會不斷增加,容易導致 ⛔️ 網絡阻塞
我們就可以在觸發(fā)事件的時候就馬上執(zhí)行任務,然后設定時間間隔限制,在這段時間內不管用戶如何進行滾動都忽視操作
在時間到了以后如果監(jiān)測到用戶有滾動行為,再次執(zhí)行任務。并且設置時間聞隔
首先,寫個改變頁面尺寸的同時改變頁面背景顏色的代碼
function coloring() { let r = Math.floor(Math.random() * 255) let g = Math.floor(Math.random() * 255) let b = Math.floor(Math.random() * 255) document.body.style.background = `rgb(${r}, ${g}, $)` } window.addEventListener('resize', coloring)
function throttle(func, delay) { let timer return function () { timer = setTimeout(_ => { func() }, delay) } } window.addEventListener('resize', throttle(coloring, 2000))
判斷觸發(fā)的事件是否在時間間隔內
- 不在:觸發(fā)事件
- 在:不觸發(fā)事件
function throttle(func, delay) { let timer return function () { // timer 被賦值了,直接返回,即不執(zhí)行任務 if (timer) { return } // 此時 timer 沒被賦值,或 timer 已經執(zhí)行完了 // 為 timer 賦值進行延時執(zhí)行 timer = setTimeout(_ => { func() // 延遲執(zhí)行以后我們要清空timer的值 timer = null }, delay) } } window.addEventListener('resize', throttle(coloring, 2000))
解決 this 指向(雖然當前的這個例子就是在 Window 下的)
function throttle(func, delay) { let timer return function () { let context = this let args = arguments // timer 被賦值了,直接返回,即不執(zhí)行任務 if (timer) { return } // 此時 timer 沒被賦值,或 timer 已經執(zhí)行完了 // 為 timer 賦值進行延時執(zhí)行 timer = setTimeout(_ => { func.apply(context, args) // 延遲執(zhí)行以后我們要清空timer的值 timer = null }, delay) } } window.addEventListener('resize', throttle(coloring, 1000))
節(jié)流核心:事件間隔 另一種常見的時間間隔就是用Date對象
function throttle(func, delay) { // 我們要和前一個時間點進行比較才能確定是否已經過了時間間隔 // 在返回函數外圍,避免每次執(zhí)行都被自動修改 let pre = 0 return function () { // 保存執(zhí)行函數當時的時間 let now = new Date() // 剛開始,一定會執(zhí)行 if (now - pre > delay) { // 已經過了時間間隔,就可以執(zhí)行函數了 func() // 執(zhí)行后,重新設置間隔點 pre = now } } } window.addEventListener('resize', throttle(coloring, 1000))
解決參數問題
function throttle(func, delay) { let pre = 0 return function () { let context = this let args = arguments let now = new Date() if (now - pre > delay) { func.apply(context, args) pre = now } } } window.addEventListener('resize', throttle(coloring, 1000))
總結
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注腳本之家的更多內容!
- JavaScript函數防抖動debounce
- 通過實例講解JS如何防抖動
- JavaScript運動框架 解決防抖動問題、懸浮對聯(二)
- JavaScript中防抖和節(jié)流的區(qū)別及適用場景
- JavaScript中防抖和節(jié)流的實戰(zhàn)應用記錄
- JavaScript深入理解節(jié)流與防抖
- JavaScript防抖與節(jié)流的實現與注意事項
- JavaScript的防抖和節(jié)流一起來了解下
- JavaScript中函數的防抖與節(jié)流詳解
- javascript的防抖和節(jié)流你了解嗎
- 淺談JavaScript節(jié)流與防抖
- 關于JavaScript防抖與節(jié)流的區(qū)別與實現
- JavaScript防抖與節(jié)流詳解
- JavaScript防抖動與節(jié)流處理
相關文章
深入理解JavaScript 中的匿名函數((function() {})();)與變量的作用域
匿名函數沒有實際名字的函數,匿名函數(function() {})();是一個特殊的閉包寫法。本文蛀牙給大家介紹JavaScript 中的匿名函數((function() {})();)與變量的作用域,需要的朋友可以參考下2018-08-08