前端面試查漏補缺--(一) 防抖和節流
本系列最開始是為了自己面試準備的.後來發現整理越來越多,差不多有十二萬字元,最後決定還是分享出來給大家.
- 全: 在準備的時候,我查看了很多很多相關資料,力求做到知識點的全面覆蓋.往往你從其他面試者中,脫穎而出,就是因為你知道了其他人甚至面試官所不知道,忽略的知識點.令人印象深刻!但如果內容實在太繁多的話,我也會提供我覺得全網目前最好的文章連結.
- 準: 釋出的所有文章的所有內容,都是自己一字一句仔細讀過,揣摩過.將參考文章中的錯誤,筆誤,不詳細,難懂的地方都進行了修改整合.並補充自己工作遇到的一些建議.力求做到知識點的精準.
- 詳: 這系列文章與其他面試文章最大的區別就是在內容詳實程度上.其他面試文章都是高度總結,濃縮,適合臨時抱佛腳!但如果知識點你本身不理解,光靠死記硬背,面試官隨便深入一下你就矇蔽了!所以這系列文章,就是對常考的知識點,從源頭開始詳解,只有你真正理解,才能自如應付面試官的各種刁難.
為了分享整理出來,花費了自己大量的時間,起碼是隻自己用的三倍時間.如果喜歡的話,歡迎收藏,關注我!謝謝!
- 前端面試查漏補缺--(一) 防抖和節流
- 前端面試查漏補缺--(二) 垃圾回收機制
- 前端面試查漏補缺--(三) 跨域及常見解決辦法
- 前端面試查漏補缺--(四) 前端本地儲存
- 前端面試查漏補缺--(五) 渲染機制及重繪和迴流
- 前端面試查漏補缺--(六) 瀏覽器快取
- 前端面試查漏補缺--(七) XSS攻擊與CSRF攻擊
- 前端面試查漏補缺--(八) 前端加密
- 前端面試查漏補缺--(九) HTTP與HTTPS
- 前端面試查漏補缺--(十) 前端鑑權
- 前端面試查漏補缺--(十一) 前端軟體架構模式MVC/MVP/MVVM
- 前端面試查漏補缺--(十二) 從輸入URL到看到頁面發生的全過程(含三握手,四揮手詳解)
- 前端面試查漏補缺--(十三) 記憶體洩漏
- 前端面試查漏補缺--(十四) 演算法及排序
- 前端面試查漏補缺--(十五) Event Loop
重點!!!:以上部分在本地都是已經全部寫好了,我會分三天,分別在掘金上釋出出來.並會在星期一發一個集合篇(類似index的作用),包含系列所有的文章連結,後續最新文章的釋出訊息,所以請務必關注,收藏集合篇!!!
後續還會繼續新增設計模式 ,前端工程化 ,專案流程,部署,閉環 等內容.如果覺得內容不錯的話歡迎收藏,關注我!謝謝!
防抖和節流
相同:在不影響客戶體驗的前提下,將頻繁的回撥函式,進行次數縮減.避免大量計算導致的頁面卡頓.
不同:防抖是將多次執行變為最後一次執行,節流是將多次執行變為在規定時間內只執行一次.
防抖
定義:
指觸發事件後在規定時間內 回撥函式只能執行一次 ,如果在規定時間內又 觸發了該事件,則會重新開始算規定時間。
網上有這個比喻:函式防抖就是法師發技能的時候要讀條,技能讀條沒完再按技能就會重新整理技能,重新進行讀條。
四個字總結就是延時執行
應用場景:
兩個條件:
1,如果客戶連續的操作會導致頻繁的事件回撥(可能引起頁面卡頓).
2,客戶只關心"最後一次"操作(也可以理解為停止連續操作後)所返回的結果.
例如:
- 輸入搜尋聯想,使用者在不斷輸入值時,用防抖來節約請求資源。
- 按鈕點選:收藏,點贊,心標等
原理:
通過定時器將回調函式進行延時.如果在規定時間內繼續回撥,發現存在之前的定時器,則將該定時器清除,並重新設定定時器.這裡有個細節,就是後面所有的回撥函式都要能訪問到之前設定的定時器,這時就需要用到閉包(詳見後面提到的)
兩種版本
防抖分為兩種:
- 1)非立即執行版:事件觸發->延時->執行回撥函式;如果在延時中,繼續觸發事件,則會重新進行延時.在延時結束後執行回撥函式.常見例子:就是input搜尋框,客戶輸完過一會就會自動搜尋
- 2)立即執行版:事件觸發->執行回撥函式->延時;如果在延時中,繼續觸發事件,則會重新進行延時.在延時結束後,並不會執行回撥函式.常見例子:就是對於按鈕防點選.例如點贊,心標,收藏等有立即反饋的按鈕.
實現程式碼及思路:
//非立即執行版: //首先準備我們要使用的回撥函式 function shotCat (content) { console.log('shotCat出品,必屬精品!必須點贊!(滑稽)') } //然後準備包裝函式: //1,儲存定時器標識 //2,返回閉包函式: 1)對定時器的判斷清除;2)一般還需要儲存函式的引數(一般就是事件返回的物件)和上下文(定時器存在this隱式丟失,詳情可以看我不知道的js上) //最後補充一句,這裡不建議通過定義一個全域性變數來替代閉包儲存定時器標識. function debounce(fun, delay = 500) { //let timer = null 儲存定時器 return function (args) { let that = this let _args = args //這裡對定時器的設定有兩種方法,第一種就是將定時器儲存在函式(函式也是物件)的屬性上, //這種寫法,很簡便,但不是很常用 clearTimeout(fun.timer) fun.timer = setTimeout(function () { fun.call(that, _args) }, delay) //另外一種寫法就是我們比較常見的 //if (timer) clearTimeout(timer);相比上面的方法,這裡多一個判斷 //timer = setTimeout(function () { //fun.call(that, _args) //}, delay) } } //接著用變數儲存儲存 debounce 返回的帶有延時功能的函式 let debounceShotCat = debounce(shotCat, 500) //最後新增事件監聽 回撥debounceShotCat 並傳入事件返回的物件 let input = document.getElementById('debounce') input.addEventListener('keyup', function (e) { debounceShotCat(e.target.value) }) //帶有立即執行選項的防抖函式: //思路和上面的大致相同,如果是立即執行,則定時器中不再包含回撥函式,而是在回撥函式執行後,僅起到延時和重置定時器標識的作用 function debounce(fun, delay = 500,immediate = true) { let timer = null //儲存定時器 return function (args) { let that = this let _args = args if (timer) clearTimeout(timer);//不管是否立即執行都需要首先清空定時器 if (immediate) { if ( !timer) fun.apply(that, _args)//如果定時器不存在,則說明延時已過,可以立即執行函式 //不管上一個延時是否完成,都需要重置定時器 timer = setTimeout(function(){ timer = null; //到時間後,定時器自動設為null,不僅方便判斷定時器狀態還能避免記憶體洩露 }, delay) } else { //如果是非立即執行版,則重新設定定時器,並將回撥函式放入其中 timer = setTimeout(function(){ fun.call(that, _args) }, delay); } } } 複製程式碼
節流
定義:
當持續觸發事件時,在規定時間段內只能呼叫一次回撥函式。如果在規定時間內又 觸發了該事件,則什麼也不做,也不會重置定時器.
與防抖比較:
防抖是將多次執行變為最後一次執行,節流是將多次執行變為在規定時間內只執行一次.一般不會重置定時器.
即不會if (timer) clearTimeout(timer);
(時間戳+定時器版除外
)
應用場景:
兩個條件:
1,客戶連續頻繁地 觸發事件
2,客戶不再只關心"最後一次"操作後的結果反饋.而是在操作過程中持續的反饋.
例如:
- 滑鼠不斷點選觸發,點選事件在規定時間內只觸發一次(單位時間內只觸發一次)
- 監聽滾動事件,比如是否滑到底部自動載入更多,用throttle來判斷
注意:何為連續頻繁地觸發事件,就是事件觸發的時間間隔至少是要比規定的時間要短.
原理:
節流有兩種實現方式
-
- 時間戳方式:通過閉包儲存上一次的時間戳,然後與事件觸發的時間戳比較.如果大於規定時間,則執行回撥.否則就什麼都不處理.
- 特點:一般第一次會立即執行 ,之後連續頻繁地觸發事件,也是超過了 規定時間才會執行一次。最後一次觸發事件,也不會執行(說明:如果你最後一次觸發時間大於規定時間,這樣就算不上連續頻繁觸發了).
-
- 定時器方式:原理與防抖類似.通過閉包儲存上一次定時器狀態.然後事件觸發時,如果定時器為null(即代表此時間隔已經大於規定時間),則設定新的定時器.到時間後執行回撥函式,並將定時器置為null.
- 特點:當第一次觸發事件時,不會立即執行函式,到了規定時間後才會執行。 之後連續頻繁地觸發事件,也是到了 規定時間才會執行一次(因為定時器)。當最後一次停止觸發後,由於定時器的延時,還會執行一次回撥函式(那也是上一次成功成功觸發執行的回撥,而不是你最後一次觸發產生的)。一句話總結就是延時回撥,你能看到的回撥都是上次成功觸發產生的,而不是你此刻觸發產生的.
- 說明: 這兩者最大的區別:是時間戳版的函式觸發是在規定時間開始的時候,而定時器版的函式觸發是在規定時間結束的時候。 其他差異可以看我加粗的字. 具體理解請結合後面的程式碼例項,
實現程式碼及思路:
//時間戳版: //這裡fun指的就是回撥函式,我就不寫出來了 function throttle(fun, delay = 500) { let previous = 0;//記錄上一次觸發的時間戳.這裡初始設為0,是為了確保第一次觸發產生回撥 return function(args) { let now = Date.now(); //記錄此刻觸發時的時間戳 let that = this; let _args = args; if (now - previous > delay) {//如果時間差大於規定時間,則觸發 fun.apply(that, _args); previous = now; } } } //定時器版: function throttle(fun, delay = 500) { let timer; return function(args) { let that = this; let _args = args; if (!timer) {//如果定時器不存在,則設定新的定時器,到時後,才執行回撥,並將定時器設為null timer = setTimeout(function(){ timer = null; fun.apply(that, _args) }, delay) } } } //時間戳+定時器版: 實現第一次觸發可以立即響應,結束觸發後也能有響應 (該版才是最符合實際工作需求) //該版主體思路還是時間戳版,定時器的作用僅僅是執行最後一次回撥 function throttle(fun, delay = 500) { let timer = null; let previous = 0; return function(args) { let now = Date.now(); let remaining = delay - (now - previous); //距離規定時間,還剩多少時間 let that = this; let _args = args; clearTimeout(timer);//清除之前設定的定時器 if (remaining <= 0) { fun.apply(that, _args); previous = Date.now(); } else { timer = setTimeout(fun, remaining); //因為上面新增的clearTimeout.實際這個定時器只有最後一次才會執行 } } } 複製程式碼