js中如何判斷session的快取資料是否清空_一文搞懂JS系列(五)之閉包應用-防抖,節流...
技術標籤:js中如何判斷session的快取資料是否清空js判斷定時器是否啟動js判斷時間是否大於當前時間js定時器一直開啟會增加記憶體麼js延時函式js怎麼重複執行方法直到成功
寫在最前面:這是我即將開始寫的一個系列,主要是在框架橫行的時代,雖然上班用的是框架,但是對於面試,以及技術進階,JS基礎知識的鋪墊是錦上添花,也是不得不學習的一塊知識,雖然開汽車的不需要很懂汽車,只需要掌握汽車的常用功能即可。但是如果你懂汽車,那你也能更好地開車,同理。當然,一篇文章也不會光光只講一個知識點,一般會將有關聯的知識點串聯起來,一邊記錄自己的學習,一邊分享自己的學習,互勉!如果可以的話,也 請給我點個贊,你的點贊也能讓我更加努力地更新!
概覽
- 食用時間: 10-15分鐘
- 難度: 簡單,別跑,看完再走
- 食用價值: JS效能優化
- 食材
先來看一段程式碼,這會是一個貫穿全文的案例,程式碼如下:
<div id="content" style="height:150px;line-height:150px; text-align:center; color: #fff;background-color:black; font-size:80px;"></div> <script> let num = 1; const content = document.getElementById('content'); function count() { content.innerHTML = num++; }; content.onmousemove = count; </script>
可以看到,在黑色色塊中移動的同時, addCount
函式被瘋狂執行,接下來,我們來引入今天的主角,防抖 和 節流
防抖
定義
將多次執行變為最後一次執行或執行,你可以理解為防止手抖
使用場景
- 搜尋框搜尋輸入。只需使用者最後一次輸入完,再發送請求
- 手機號、郵箱驗證輸入檢測
- 視窗大小Resize。只需視窗調整完成後,計算視窗大小。防止重複渲染
實現方式
- 非立即執行版
這應該是最基礎也是最常用的一個版本,先來看下程式碼
function debounce(func,wait) { let timeout; //延時器變數 return function () { const context = this; //改變this指向 const args = [...arguments]; //函式入參,另外使用了...擴充套件運算子 if (timeout) clearTimeout(timeout); //先判斷有沒有延時器,有則清空,畢竟要最後一次執行 timeout = setTimeout(() => { func.apply(context, args) //apply呼叫傳入方法 }, wait); } }
可以看到,方法 debounce()
有兩個入參,一個方法名 func
, 以及一個延時時間 wait
,單位 ms
。
接下來,使用 content.onmousemove = debounce(count,1000);
呼叫我們新寫的非立即執行版的防抖,先來看下實際的執行效果,可以看到事件觸發了以後,只有在觸發以後的1s內不再觸發,才會執行相應的方法,也就是 count++
。如果停止時間間隔小於 wait
的值並且再次觸發,那麼將重新計算執行時間,計時器結束以後,再執行方法。總結而言就是觸發事件後函式不會立即執行,而是在 n 秒後執行,如果在 n 秒內又觸發了事件,則會重新計算函式執行時間,也就是方法的執行是非立即執行的
整個方法的核心思想就是依靠變數 timeout
,用來控制當前是否存在定時器,如果有,則清空,清空玩以後再繼續建立一個。所以,在多次執行的同時,不斷清空再新建,直到停止執行以後,在停止執行以後的 wait
毫秒以後,延時器就會成功生效,方法就會被觸發,也就是所謂的非立即執行,畢竟,還要等待延時器的延時 wait
。
- 立即執行版
立即執行版就是在觸發事件後函式會立即執行,然後 n 秒內不觸發事件才能繼續執行函式的效果,程式碼如下:
function debounce(func,wait,...args){
let timeout; //延時器變數
return function(){
const context = this;
if (timeout) clearTimeout(timeout);
let callNow = !timeout; //是否立即執行
timeout = setTimeout(() => {
timeout = null;
},wait)
if(callNow) func.apply(context,args)
}
}
可以看到的是, timeout
依然是延時器,主要核心控制是靠 callNow
① 在剛初始化的時候,沒有定時器,所以剛開始callNow=!timeout
執行完以後,callNow
為true
,再設定一個延時器,然後直接執行方法,這就是所謂的 立即執行
② 第二次的時候在進入的時候,if (timeout)
為真,將定時器進行清空,callNow=!timeout
為假,條件不成立
③if(callNow)
不成立,函式不執行,因為timeout = null
,往後將不再執行函式,直到延時器完成呼叫timeout = null
之後再觸發事件
④ 觸發之後,timeout = null
,callNow
賦值為真,if(callNow)
條件再次符合,完成執行函式
關於上面有一點, clearTimeout(timeout)
以後,console.log(timeout)
輸出為 1
不相信的可以看一下下面的程式碼輸出
let timer=setTimeout(()=>{
},1000);
clearTimeout(timer);
console.log(!timer); //false
最後,讓我們再來看一下實際使用效果,可以看到的是,觸發事件後函式會立即執行,然後 n 秒內不觸發事件才能繼續執行函式的效果
節流
定義
將多次執行變為每隔一段時間執行一次
使用場景
- 滾動載入,載入更多或滾到底部監聽
實現方式
- 時間戳版(立即執行版)
在持續觸發事件的過程中,函式會立即執行,並且每隔一段時間執行一次,程式碼如下:
function throttle(func, wait, ...args){
let pre = 0;
return function(){
const context = this;
let now = Date.now();
if (now - pre >= wait){
func.apply(context, args);
pre = Date.now();
}
}
}
① 首先定義了一個只有完成函式呼叫才更新當前時間的變數pre
,然後定義了一個實時更新的當前時間now
② 進入第一次計算時間間隔,now - pre >= wait
是必定成立的,所以函式會 立即觸發
③ 觸發完了以後,將pre
的值進行更新,之後,now
的值會進行實時更新
④ 直到now - pre >= wait
的條件成立,也就是現在的時間距離上次觸發的時間大於等於wait
的等待時間,函式會再次觸發, (畢竟只要函式不觸發,pre
的值不更新,而now一直在實時更新,時間長了,條件肯定會成立的)
⑤ 以此類推,完成了事件一直在觸發,首次立即執行函式,之後函式只會隔一段時間執行
分析完了程式碼,讓我們來看看實際執行效果,果然和我們的分析如出一轍:
- 延時器版(非立即執行版)
在持續觸發事件的過程中,函式不會立即執行,並且每隔一段時間執行一次,在停止觸發事件後,函式還會再執行一次,程式碼如下:
function throttle(func, wait, ...args){
let timeout;
return function(){
const context = this;
if(!timeout){
timeout = setTimeout(() => {
timeout = null;
func.apply(context,args);
},wait)
}
}
}
① 首先定義了一個延時器變數timeout
,先判斷是否有延時器,沒有則建立,所以第一次進入函式的時候,會先建立一個延時器
② 再次進入函式的時候,因為當前已經存在延時器了,所以什麼都不做
③ 什麼都不做直到延時器的時間結束,函式開始執行, 將timeout
進行清空並且執行函式
④ 清空以後,再一次判斷,if(!timeout)
條件成立,繼續建立延時器
⑤ 以此類推, 有延時器就什麼都不做,沒有了延時器則建立
⑥ 即使不觸發事件,延時器仍然存在,所以, 停止觸發事件以後,函式仍然會再執行一次
分析完了程式碼,讓我們來看看實際執行效果,果然和我們的分析如出一轍:
系列目錄
- 一文搞懂JS系列(一)之編譯原理,作用域,作用域鏈,變數提升,暫時性死區
- 一文搞懂JS系列(二)之JS記憶體生命週期,棧記憶體與堆記憶體,深淺拷貝
- 一文搞懂JS系列(三)之垃圾回收機制,記憶體洩漏,閉包
- 一文搞懂JS系列(四)之閉包應用-柯里化,偏函式
- 一文搞懂JS系列(四)之閉包應用-防抖,節流