自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

前端性能優(yōu)化-每一個前端開發(fā)者需要知道的防抖與節(jié)流知識

開發(fā) 前端
防抖和節(jié)流都是應用在高頻事件觸發(fā)場景中,例如 scroll(滾動加載、回到頂部)、input(聯(lián)想輸入) 事件等。

[[440013]]

本文轉載自微信公眾號「編程界」,作者五月君。轉載本文請聯(lián)系編程界公眾號。

防抖和節(jié)流都是應用在高頻事件觸發(fā)場景中,例如 scroll(滾動加載、回到頂部)、input(聯(lián)想輸入) 事件等。防抖和節(jié)流核心實現(xiàn)思想是在事件和函數(shù)之間增加了一個控制層,達到延遲執(zhí)行的功能,目的是防止某一時間內(nèi)頻繁執(zhí)行一些操作,造成資源浪費。

事件與函數(shù)之間的控制層通常有兩種實現(xiàn)方式:一是使用定時器,每次事件觸發(fā)時判斷是否已經(jīng)存在定時器,是本文我們實現(xiàn)的方式。另外一種是記錄上一次事件觸發(fā)的時間戳,每次事件觸發(fā)時判斷當前時間戳距離上次執(zhí)行的時間戳之間的一個差值(deplay - (now - previous)),是否達到了設置的延遲時間。

可視化效果對比

下圖是通過一個可視化工具 debounce_throttle 截取的一個效果圖,展示了移動鼠標事件在常規(guī)操作、防抖處理(debounce)、**節(jié)流處理(throttle)**三種情況下的一個對比。

防抖(debounce)

防抖是在事件觸的指定時間后執(zhí)行回掉函數(shù),如果指定時間內(nèi)再次觸發(fā)事件,按照最后一次重新計時。

生活場景示例:公交車到站點后,師傅不會上一個人就立馬關閉車門起步,會等待最后一個人上去了或車上人已經(jīng)滿了,才會關閉車門起步。

聯(lián)想輸入 - 常規(guī)示例

例如搜索框聯(lián)想提示,當我們輸入數(shù)據(jù)后,可能會請求接口獲取數(shù)據(jù),如果沒有做任何處理,當在輸入開始時就會不斷的觸發(fā)接口請求,這中間必然會造成資源的浪費,如果這樣頻繁操作 DOM 也是不可取的。

  1. // Bad code 
  2. <html> 
  3.   <body> 
  4.     <div> search: <input id="search" type="text"> </div> 
  5.     <script> 
  6.       const searchInput = document.getElementById("search"); 
  7.       searchInput.addEventListener('input', ajax); 
  8.       function ajax(e) { // 模仿數(shù)據(jù)查詢 
  9.         console.log(`Search data: ${e.target.value}`); 
  10.       } 
  11.     </script> 
  12.   </body> 
  13. </html> 

 

 

 

 

上面這段代碼我們沒有做過任何優(yōu)化,使用 ajax() 方法模擬數(shù)據(jù)請求,讓我們看下執(zhí)行效果。

常規(guī)聯(lián)想輸入操作.gif

如果是調(diào)用的真實接口,從輸入的那一刻起就會不停掉用服務端接口,浪費不必要的性能,還很容易觸發(fā)接口的限流措施,例如 Github 提供的 API 會有每小時最大請求數(shù)限制。

聯(lián)想輸入 - 防抖處理示例

讓我們實現(xiàn)一個防抖函數(shù)(**debounce****)**優(yōu)化下上面的代碼。**原理是通過標記,判斷指定的時間內(nèi)是否存在多次調(diào)用,當存在多次調(diào)用時清除掉上一次的定時器,重新開始計時,在指定的時間內(nèi)如果沒有再次調(diào)用,就執(zhí)行傳入的回調(diào)函數(shù) ****fn**。

  1. function debounce(fn, ms) { 
  2.   let timerId; 
  3.  
  4.   return (...args) => { 
  5.     if (timerId) { 
  6.       clearTimeout(timerId); 
  7.     } 
  8.  
  9.     timerId = setTimeout(() => { 
  10.       fn(...args); 
  11.     }, ms); 
  12.   } 

這對于搜索場景是比較合適的,我們希望以最后一次輸入結果為準,修改最開始的聯(lián)想輸入示例。

  1. const handleSearchRequest = debounce(ajax, 500) 
  2.  
  3. searchInput.addEventListener('input', handleSearchRequest); 

這次就好多了,當連續(xù)輸入停頓時以最后一次的輸入接口為準請求接口,避免了不停的刷新接口。

聯(lián)想輸入-防抖.gif

適當?shù)臅r候記得要清除事件,例如 React 中,我們在組件掛載時監(jiān)聽 input,同樣的組件卸載時也要清除對應的事件監(jiān)聽器函數(shù)。

  1. componentDidMount() { 
  2.   this.handleSearchRequest = debounce(ajax, 500) 
  3.  searchInput.addEventListener('input', this.handleSearchRequest); 
  4.  
  5. componentWillUnmount() { 
  6.   searchInput.removeEventListener('input', this.handleSearchRequest); 

節(jié)流(throttle)

節(jié)流是在事件觸發(fā)后,在指定的間隔時間內(nèi)執(zhí)行回調(diào)函數(shù)。

生活場景示例:當我們乘坐地鐵時,列車總是按照指定的間隔時間每 5 分鐘(也許是其它時間)這樣運行,當時間到達之后,列車就要開走了。

滾動到頂部 - 常規(guī)示例

例如,頁面有很多個列表項,當我們向下滾動之后希望出現(xiàn)一個 Top 按鈕 點擊之后能夠回到頂部,這時我們需要獲取滾動位置與頂部的距離判斷是否展示 Top 按鈕。

  1. <body> 
  2.   <div id="container"></div> 
  3.   <script> 
  4.     const container = document.getElementById('container'); 
  5.     window.addEventListener('scroll', handleScrollTop); 
  6.     function handleScrollTop() { 
  7.       console.log('scrollTop: ', document.body.scrollTop); 
  8.       if (document.body.scrollTop > 400) { 
  9.         // 處理展示按鈕操作 
  10.       } else { 
  11.         // 處理不展示按鈕操作 
  12.       } 
  13.     } 
  14.   </script> 
  15. </body> 

 

 

 

 

 

 

可以看到,如果不加任何處理,滾動一下可能就會觸發(fā)上百次,每次都去做處理,顯然是白白浪費性能的。

滾動未處理節(jié)流.png

滾動到頂部 - 節(jié)流處理示例

實現(xiàn)一個簡單的節(jié)流(throttle)函數(shù),與防抖很相似,區(qū)別的地方是,這里通過標志位判斷是否已經(jīng)被觸發(fā),當已經(jīng)觸發(fā)后,再進來的請求直接結束掉,直到上一次指定的間隔時間到達且回調(diào)函數(shù)執(zhí)行之后,再接受下一個處理。

  1. function throttle(fn, ms) { 
  2.   let flag = false
  3.   return (...args) => { 
  4.     if (flag) return
  5.     flag = true
  6.     setTimeout(() => { 
  7.       fn(...args) 
  8.       flag = false
  9.     }, ms); 
  10.   } 

改造下上面的示例,再來看看執(zhí)行結果。

  1. const handleScrollTop = throttle(() => { 
  2.   console.log('scrollTop: ', document.body.scrollTop); 
  3.   // todo: 
  4. }, 500); 
  5. window.addEventListener('scroll', handleScrollTop); 

與上面 “常規(guī)滾動到頂部示例” 做對比,現(xiàn)在效果已經(jīng)好多了。

滾動到頂部-節(jié)流處理.gif

記得清除事件

以 React 為例,組件掛載時我們監(jiān)聽 window 的 scroll 事件,在組件卸載時記得要移除對應的事件監(jiān)聽器函數(shù)。如果組件卸載時忘記移除,原先 A 頁面引入了 ScrollTop 組件,單頁面應用跳轉到 B 頁面后,雖然 B 頁面沒有引入 ScrollTop 組件,但是也會受到影響,因為該事件已經(jīng)在 window 全局對象注冊了,另外這樣也存在內(nèi)存泄漏。

  1. class ScrollTop extends PureComponent { 
  2.   componentDidMount() { 
  3.     this.handleScrollTop = throttle(this.props.updateScrollTopValue, 500); 
  4.     window.addEventListener('scroll', this.handleScrollTop); 
  5.   } 
  6.  
  7.   componentWillUnmount() { 
  8.     window.removeEventListener('scroll', this.handleScrollTop); 
  9.   } 
  10.    
  11.   // ... 

requestAnimationFrame

requestAnimationFrame 是瀏覽器提供的一個 API,它的應用場景是告訴瀏覽器,我需要運行一個動畫。該方法會要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動畫。這個 API 在 JavaScript 異步編程指南 - 探索瀏覽器中的事件循環(huán)機制 中有講過。

它會受到瀏覽器的刷新頻率影響,如果是 60fps 那就是每間隔 16.67ms 執(zhí)行一次,如果在 16.67ms 內(nèi)有多次 DOM 操作,也是不會渲染多次的。

當瀏覽器的刷新頻率為 60fps 時等價于 throttle(fn, 16.67)。在使用時需要先處理下,不能讓它立即執(zhí)行,由事件觸發(fā)。

  1. const handleScrollTop = () => requestAnimationFrame(() => { 
  2.   console.log('scrollTop: ', document.body.scrollTop); 
  3.   // todo: 
  4. }); 
  5. window.addEventListener('scroll', handleScrollTop); 

requestAnimationFrame 這個是瀏覽器的 API,在 Node.js 中是不支持的。

社區(qū)工具集支持

社區(qū)中一些 JavaScript 的工具集框架,也都提供了防抖與節(jié)流的支持,例如 underscorejs、lodash。

剛開始有提到,另外一種實現(xiàn)方式是記錄上一次事件觸發(fā)的時間戳,每次事件觸發(fā)時判斷當前時間戳距離上次執(zhí)行的時間戳之間的一個差值,來判斷是否達到了設置的延遲時間,以 underscorejs throttle 實現(xiàn)為例,只保留部分代碼示例,一個關鍵代碼片段是 remaining = wait - (_now - previous)。

  1. // https://github.com/jashkenas/underscore/blob/master/modules/throttle.js#L23 
  2. export default function throttle(func, wait, options) { 
  3.   var timeout, context, args, result; 
  4.   var previous = 0; 
  5.    
  6.   var throttled = function() { 
  7.     var _now = now(); 
  8.     if (!previous && options.leading === false) previous = _now; 
  9.     var remaining = wait - (_now - previous); 
  10.     context = this; 
  11.     args = arguments; 
  12.     if (remaining <= 0 || remaining > wait) { 
  13.       if (timeout) { 
  14.         clearTimeout(timeout); 
  15.         timeout = null
  16.       } 
  17.       previous = _now; 
  18.       result = func.apply(context, args); 
  19.       if (!timeout) context = args = null
  20.     } 
  21.   }; 
  22.  
  23.   return throttled; 

總結

防抖是在事件觸的指定時間后執(zhí)行回掉函數(shù),如果指定時間內(nèi)再次觸發(fā)事件,按照最后一次重新計時。節(jié)流是在事件觸發(fā)后的間隔時間內(nèi)執(zhí)行回調(diào)函數(shù)。這兩個概念在前端開發(fā)中都是會遇到的,選擇合理的方案解決實際問題。

防抖與節(jié)流還不是太理解的,對著文中的示例自己實踐下,有什么疑問在留言區(qū)提問。

Reference

https://jinlong.github.io/2016/04/24/Debouncing-and-Throttling-Explained-Through-Examples/

http://demo.nimius.net/debounce_throttle/

JavaScript 異步編程指南 - 探索瀏覽器中的事件循環(huán)機制

 

責任編輯:武曉燕 來源: 編程界
相關推薦

2025-02-25 08:30:00

前端開發(fā)VSCode

2024-10-12 09:33:24

消息隊列多線程并行編程

2019-04-16 08:50:56

WebHTTP緩存

2021-12-24 11:24:59

React HackReact JavaScript

2013-06-28 14:19:20

2015-03-10 09:23:21

前端開發(fā)Sublime插件Sublime

2021-11-19 09:01:09

防抖節(jié)流前端

2019-03-12 10:38:18

前端開發(fā)Nginx

2016-02-22 15:09:19

Android項目管理技巧

2023-06-05 16:50:06

開發(fā)TypeScriptJavaScript

2013-04-16 09:30:09

前端開發(fā)Web網(wǎng)頁設計

2010-07-30 16:27:06

Flex開發(fā)

2024-04-26 13:36:01

2023-08-10 08:31:53

工具實用網(wǎng)站

2022-10-20 15:12:43

JavaScript技巧開發(fā)

2023-06-27 07:55:03

前端開發(fā)工具

2021-01-26 09:13:07

前端開發(fā)者程序員

2014-07-17 09:31:50

iOS8SDK

2010-03-01 10:20:27

Flex

2011-05-26 11:13:36

Flex
點贊
收藏

51CTO技術棧公眾號