深入理解javascript函數進階系列第三篇——函數節流和函數防抖

来源:http://www.cnblogs.com/xiaohuochai/archive/2017/12/12/8024563.html
-Advertisement-
Play Games

[1]常見場景 [2]函數防抖 [3]函數節流 [4]數組分塊 [5] ...


前面的話

  javascript中的函數大多數情況下都是由用戶主動調用觸發的,除非是函數本身的實現不合理,否則一般不會遇到跟性能相關的問題。但在一些少數情況下,函數的觸發不是由用戶直接控制的。在這些場景下,函數有可能被非常頻繁地調用,而造成大的性能問題。解決性能問題的處理辦法就是函數節流和函數防抖。本文將詳細介紹函數節流和函數防抖

 

常見場景

  下麵是函數被頻繁調用的常見的幾個場景

  1、mousemove事件。如果要實現一個拖拽功能,需要一路監聽 mousemove 事件,在回調中獲取元素當前位置,然後重置 dom 的位置來進行樣式改變。如果不加以控制,每移動一定像素而觸發的回調數量非常驚人,回調中又伴隨著 DOM 操作,繼而引發瀏覽器的重排與重繪,性能差的瀏覽器可能就會直接假死。

  2、window.onresize事件。為window對象綁定了resize事件,當瀏覽器視窗大小被拖動而改變的時候,這個事件觸發的頻率非常之高。如果在window.onresize事件函數里有一些跟DOM節點相關的操作,而跟DOM節點相關的操作往往是非常消耗性能的,這時候瀏覽器可能就會吃不消而造成卡頓現象

  3、射擊游戲的 mousedown/keydown 事件(單位時間只能發射一顆子彈)

  4、搜索聯想(keyup事件)

  5、監聽滾動事件判斷是否到頁面底部自動載入更多(scroll事件)

  對於這些情況的解決方案就是函數節流(throttle)或函數去抖(debounce),核心其實就是限制某一個方法的頻繁觸發

 

函數防抖

  函數防抖的原理是將即將被執行的函數用setTimeout延遲一段時間執行。對於正在執行的函數和新觸發的函數衝突問題有兩種處理,也分別對應了定時器管理的兩種機制

  第一種是只要當前函數沒有執行完成,任何新觸發的函數都會被忽略,簡易代碼如下

function debounce(method, context) {
  //忽略新函數
  if(method.tId){
    return false;
  }
  method.tId = setTimeout(function() {
    method.call(context);
  }, 1000);
}

  第二種是只要有新觸發的函數,就立即停止執行當前函數,轉而執行新函數,簡易代碼如下

function debounce(method, context) {
 //停止當前函數
  clearTimeout(method.tId);
  method.tId = setTimeout(function() {
    method.call(context);
  }, 1000);
}

  當然,不論是哪種處理,函數去抖的目的是讓要執行的函數停止一段時間之後才執行

  下麵是一個比較完整的防抖函數(debounce),該函數接受2個參數,第一個參數為需要被延遲執行的函數,第二個參數為延遲執行的時間

var debounce = function ( fn, interval ) {
  var  _self = fn,    // 保存需要被延遲執行的函數引用
      timer,    // 定時器
      firstTime = true;    // 是否是第一次調用
  return function () {
    var args = arguments,
    _me = this;
    if ( firstTime ) {    // 如果是第一次調用,不需延遲執行
      _self.apply( me, args);
      return firstTime = false;
    }
    if ( timer ) {    // 如果定時器還在,說明前一次延遲執行還沒有完成
      return false;
    }
    timer = setTimeout(function () { // 延遲一段時間執行
      clearTimeout(timer); 
      timer = null;
      _self.apply(_me, args);
    }, interval || 500 );
  };
};
window.onresize = debounce(function(){ 
  console.log( 1 );
}, 500 );

 

函數節流

  函數節流使得連續的函數執行,變為固定時間段間斷地執行。關於節流的實現,有兩種主流的實現方式,一種是使用時間戳,一種是設置定時器

【使用時間戳】

  觸發事件時,取出當前的時間戳,然後減去之前的時間戳(最一開始值設為 0 ),如果大於設置的時間周期,就執行函數,然後更新時間戳為當前的時間戳,如果小於,就不執行

function throttle(func, wait) {
    var context, args;
    var previous = 0;
    return function() {
        var now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}

【使用定時器】

  觸發事件時,設置一個定時器,再觸發事件的時候,如果定時器存在,就不執行,直到定時器執行,然後執行函數,清空定時器,這樣就可以設置下個定時器

function throttle(func, wait) {
    var timeout,args,context;
    var previous = 0;
    return function() {
        context = this;
        args = arguments;
        if (!timeout) {
            timeout = setTimeout(function(){
                timeout = null;
                func.apply(context, args)
            }, wait)
        }
    }
}

 

數組分塊

  在前面關於函數節流和函數防抖的討論中,提供了限制函數被頻繁調用的解決方案。下麵將遇到另外一個問題,某些函數確實是用戶主動調用的,但因為一些客觀的原因,這些函數會嚴重地影響頁面性能

  一個例子是創建WebQQ的QQ好友列表。列表中通常會有成百上千個好友,如果一個好友用一個節點來表示,在頁面中渲染這個列表的時候,可能要一次性往頁面中創建成百上千個節點

  在短時間內往頁面中大量添加DOM節點顯然也會讓瀏覽器吃不消,看到的結果往往就是瀏覽器的卡頓甚至假死。代碼如下:

var ary = [];
for ( var i = 1; i <= 1000; i++ ){
  ary.push( i );    // 假設 ary 裝載了 1000 個好友的數據
};
var renderFriendList = function( data ){
  for ( var i = 0, l = data.length; i < l; i++ ){
    var div = document.createElement( 'div' );
    div.innerHTML = i;
    document.body.appendChild( div );
  }
};
renderFriendList( ary );

  這個問題的解決方案之一是數組分塊技術,下麵的timeChunk函數讓創建節點的工作分批進行,比如把1秒鐘創建1000個節點,改為每隔200毫秒創建8個節點

  數組分塊是一種使用定時器分割迴圈的技術,為要處理的項目創建一個隊列,然後使用定時器取出下一個要處理的項目進行處理,接著再設置另一個定時器

  在數組分塊模式中,array變數本質上就是一個“待辦事宜”列表,它包含了要處理的項目。使用shift()方法可以獲取隊列中下一個要處理的項目,然後將其傳遞給某個函數。如果在隊列中還有其他項目,則設置另一個定時器,並通過arguments.callee調用同一個匿名函數

  數組分塊的重要性在於它可以將多個項目的處理在執行隊列上分開,在每個項目處理之後,給予其他的瀏覽器處理機會運行,這樣就可能避免長時間運行腳本的錯誤。一旦某個函數需要花50ms以上的時間完成,那麼最好看看能否將任務分割為一系列可以使用定時器的小任務

  下麵是數組分塊模式的簡易代碼

function chunk(array,process,context){
    setTimeout(function(){
        //取出下一個條目並處理
        var item = array.shift();
        process.call(context,item);
        //若還有條目,再設置另一個定時器
        if(array.length > 0){
            setTimeout(arguments.callee,100);
        }
    },100);    
}
var data = [1,2,3,4,5,6,7,8,9,0];
function printValue(item){
    var div = document.getElementById('myDiv');
    div.innerHTML += item + '<br>';
}
chunk(data.concat(),printValue);

  下麵是數組分塊的詳細代碼,timeChunk函數接受3個參數,第1個參數是創建節點時需要用到的數據,第2個參數是封裝了創建節點邏輯的函數,第3個參數表示每一批創建的節點數量

var timeChunk = function( ary, fn, count ){ 
  var obj,t;
  var len = ary.length;
  var start = function(){
    for ( var i = 0; i < Math.min( count || 1, ary.length ); i++ ){ 
      var obj = ary.shift();
      fn( obj );
    }
  };
  return function(){
    t = setInterval(function(){
      if ( ary.length === 0 ){ // 如果全部節點都已經被創建好
        return clearInterval( t );
      }
      start();
    }, 200 );    // 分批執行的時間間隔,也可以用參數的形式傳入
  };
};

  最後進行一些小測試,假設有1000個好友的數據,利用timeChunk函數,每一批只往頁面中創建8個節點

var ary = [];
for ( var i = 1; i <= 1000; i++ ){ 
  ary.push( i );
};
var renderFriendList = timeChunk( ary, function( n ){ 
  var div = document.createElement( 'div' ); 
  div.innerHTML = n;
  document.body.appendChild( div );
}, 8 );
renderFriendList();

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 因為瀏覽器的相容問題,不同瀏覽器對有些標簽的預設值是不同的,如果沒對CSS初始化往往會出現瀏覽器之間的頁面顯示差異。 當然,初始化樣式會對SEO有一定的影響,但魚和熊掌不可兼得,但力求影響最小的情況下初始化 body, h1, h2, h3, h4, h5, h6, hr, p, blockquot ...
  • 一、數據類型: 原始數據類型:Boolean/ Null / Undefined / Number / String / Symbol(ES6中新增數據類型) 對象:Object 二、類型轉換 1)顯示類型轉換: a. Number函數 b. String函數 c.Boolean函數 2) 顯示類型 ...
  • <!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8"> <title></title></head><body> <div style="height: 40px;"> <div style="width: 1000px;margin ...
  • <!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8"> <title></title> <style type="text/css"> *{ margin: 0px; padding: 0px; list-style-type: no ...
  • 提起函數中的this是很多初學者較為苦惱的,也是很多工作一段時間的人也存在誤解的,你問this指向的是誰,大多數人會隨口一答當然是指向調用這個函數的元素,當然這也沒什麼錯,可是函數的調用方法不同this的指向就不同,下麵就看一下函數的幾種調用方法 1. 直接調用:this>Window 2.通過對象 ...
  • 2015年6月17日,ECMAScript 6發佈正式版本,即ECMAScript 2015。 函數作為js語言中的一等公民。自然Es6中推出的箭頭函數(=>)也是備受矚目的。那我們接下來看下傳說中的“箭頭函數”和function函數有什麼不同吧~ 1.this的指向,函數內置 this 的值,取決 ...
  • 前面的話 惰性函數表示函數執行的分支只會在函數第一次調用的時候執行,在第一次調用過程中,該函數會被覆蓋為另一個按照合適方式執行的函數,這樣任何對原函數的調用就不用再經過執行的分支了。本文將詳細介紹惰性函數 使用背景 因為各瀏覽器之間的行為的差異,經常會在函數中包含了大量的if語句,以檢查瀏覽器特性, ...
  • <!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8"> <title></title></head><body> <div style="border: 1px solid red; width: 1000px;margin: 0px ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...