(83)Wangdao.com第十七天_JavaScript 定時器

JavaScript 提供定時執行代碼的功能,叫作 定時器(timer)。ajax

主要由 setTimeout() setInterval() 這兩個函數來完成。它們向任務隊列添加定時任務瀏覽器

 

  • setTimeout()
        • var timerId = setTimeout( func()|code, delay);
        • 第一個參數 func() | code 是將要推遲執行的函數名或者一段代碼服務器

        • 第二個參數 delay是推遲執行的毫秒數app

 

    • 來指定某個函數或某段代碼,在多少毫秒以後執行。
    • 它返回一個整數,表示定時器的編號,之後能夠用來取消這個定時器。

 

    • 例1
      • console.log(1);
        setTimeout('console.log(2)',1000);
        console.log(3);
        // 1
        // 3
        // 2
      • 上面代碼會 先輸出 1 和 3,而後等待 1000毫秒 再輸出 2函數

    • 若是第一個參數
      • 是代碼,則必須以字符串的形式傳入
      • 是函數,則直接傳入函數名
        • function f() {
              console.log(2);
          }
          
          setTimeout(f, 1000);

           

    • 若是第二個參數省略,則默認爲 0
    • 除了前兩個參數,setTimeout(func , delayTime , argu1, argu2......) 還容許更多的參數。做爲 回調函數的實參
      • setTimeout(function (a,b) {
            console.log(a + b);
        }, 1000, 1, 1);    // 1秒鐘後打印 '2'

         

    • 若是傳入的是 某個對象的方法,在 setTimeOut() 中運行時,this 仍是會指向 window
      • var x = 1;
        
        var obj = {
            x: 2,
            y: function () {
                console.log(this.x);
            }
        };
        
        setTimeout(obj.y, 1000);    // 1    打印的是全局變量 x 

         

    • 上面代碼的解決辦法是,使用匿名函數包裹
      • var x = 1;
        
        var obj = {
            x: 2,
            y: function () {
                console.log(this.x);
            }
        };
        
        setTimeout(function () {
            obj.y();
        }, 1000);    // 2    打印的是 obj 對象的屬性 x

 

    • 另外一種解決方法是使用 bind() 函數將 obj.y() 方法綁定在 obj 上面
      • var x = 1;
        
        var obj = {
            x: 2,
            y: function () {
                console.log(this.x);
            }
        };
        
        setTimeout(obj.y.bind(obj), 1000)

 

 

  • setInterval()
    • 用法與 setTimeout() 徹底一致,
    • 區別在於
      • setInterval() 指定某個任務每隔一段時間就執行一次,也就是無限次的定時執行,直到關閉當前窗口
      • setInterval() 設置的時間間隔,要把程序運行時間算在內。
        • 好比,setInterval指定每 100ms 執行一次,每次執行須要 5ms,那麼第一次執行結束後95毫秒,第二次執行就會開始。
        • 若是某次執行耗時特別長,好比須要105毫秒,那麼它結束後,下一次執行就會當即開始。
      • var aDiv = document.getElementById('a_div');
        var opacity = 1;
        var flag = true;
        
        var brath = setInterval(function() {
            if (opacity >= 0 && flag ) {
                opacity -= 0.1;
            } else {
                opacity += 0.1;
            }
            aDiv.style.opacity = opacity;
            if(opacity < 0 || opacity > 1){
                flag = !flag;
            }
        }, 200);

        上面代碼是讓一個div 透明度慢慢下降,而後又慢慢增高。。。呼吸的效果性能

 

 

    • 實現輪詢
      • 輪詢 URL 的 Hash 值是否發生變化的例子
      • var hash = window.location.hash;
        var hashWatcher = setInterval(function() {
            if (window.location.hash != hash) {
                updatePage();
            }
        }, 1000);

         

    • 爲了確保兩次執行之間有固定的間隔
      • 能夠不用 setInterval(),而是每次執行結束後,使用 setTimeout() 指定下一次執行的具體時間
      • var i = 1;
        var timer = setTimeout(function f() {
            // ...
            timer = setTimeout(f, 2000);   // 能夠確保,下一次執行老是在本次執行結束以後的2000毫秒開始
        }, 2000);

 

  • clearTimeout() clearInterval()
    • 都是返回一個整數值,表示計數器編號
    • 將該整數傳入 clearTimeout() 和 clearInterval() 函數,就能夠取消對應的定時器
      • var id1 = setTimeout(f, 1000);
        var id2 = setInterval(f, 1000);
        
        clearTimeout(id1);
        clearInterval(id2);
        
        // 上面代碼中,回調函數f不會再執行了,由於兩個定時器都被取消了

 

    • setTimeout() 和 setInterval() 返回的整數值是連續的,
      • 也就是說,第二個 setTimeout() 方法返回的整數值,將比第一個的整數值大 1
      • function f() {}
        setTimeout(f, 1000);    // 10
        setTimeout(f, 1000);    // 11
        setTimeout(f, 1000);    // 12
      •  利用這一點,能夠寫一個函數,取消當前全部的 setTimeout() 定時器
        • (function() {
              var gid = setInterval(clearAllTimeouts, 0);
          
              function clearAllTimeouts() {
                  var id = setTimeout(function() {}, 0);
                  while (id > 0) {
                      if (id !== gid) {
                          clearTimeout(id);
                      }
                      id--;
                  }
              }
          })();

          先調用setTimeout,獲得一個計算器編號,而後把編號比它小的計數器所有取消this

 

  • debounce()
    • 用於消抖的函數

 

    • 有時,咱們不但願回調函數被頻繁調用
      • 好比,用戶填入網頁輸入框的內容,但願經過 Ajax 方法傳回服務器,jQuery 的寫法以下
        $('textarea').on('keydown', ajaxAction);

        這樣寫有一個很大的缺點,就是若是用戶連續擊鍵,就會連續觸發keydown事件,形成大量的 Ajax 通訊。spa

      • 這是沒必要要的,並且極可能產生性能問題。
      • 正確的作法應該是,
        • 設置一個門檻值,表示兩次 Ajax 通訊的最小間隔時間。
        • 若是在間隔時間內,發生新的 keydown 事件,則不觸發 Ajax 通訊,而且從新開始計時。
        • 若是過了指定時間,沒有發生新的 keydown 事件,再將數據發送出去
        • 這種作法叫作 debounce(防抖動)
          // 假定兩次 Ajax 通訊的間隔不得小於2500毫秒,上面的代碼能夠改寫成下面這樣
          
          $('textarea').on('keydown', debounce(ajaxAction, 2500));
          
          function debounce(fn, delay){
              var timer = null; // 聲明計時器
              return function() {
                  var context = this;
                  var args = arguments;
                  clearTimeout(timer);
                  timer = setTimeout(function () {
                      fn.apply(context, args);
                  }, delay);
              };
          }

          只要在2500毫秒以內,用戶再次擊鍵,就會取消上一次的定時器,而後再新建一個定時器。這樣就保證了回調函數之間的調用間隔,至少是2500毫秒code

 

  • setTimeout() 和 setInterval() 的運行機制
    • 是將指定的代碼移出本輪事件循環,等到下一輪事件循環,再檢查是否到了指定時間。
    • 若是到了,就執行對應的代碼;
    • 若是不到,就繼續等待

 

    •  這意味着,setTimeout() 和 setInterval() 指定的回調函數,必須等到本輪事件循環的全部同步任務都執行完,纔會開始執行。
      • 因爲前面的任務到底須要多少時間執行完,是不肯定的,因此沒有辦法保證,setTimeout() 和 setInterval() 指定的任務,必定會按照預約時間執行。
        setTimeout(someTask, 100);
        veryLongTask();
        
        // 上面代碼的setTimeout,指定100毫秒之後運行一個任務。
        // 可是,若是後面的veryLongTask函數(同步任務)運行時間很是長,過了100毫秒還沒法結束,那麼被推遲運行的someTask就只有等着,等到veryLongTask運行結束,才輪到它執行

         

    •  再看一個setInterval的例子
      • setInterval(function () {
            console.log(2);
        }, 1000);
        
        sleep(3000);
        
        function sleep(ms) {
            var start = Date.now();
            while ((Date.now() - start) < ms) {
            }
        }
        // 上面代碼中,setInterval要求每隔1000毫秒,就輸出一個2。
        // 可是,緊接着的sleep語句須要3000毫秒才能完成,那麼setInterval就必須推遲到3000毫秒以後纔開始生效。
        // 注意,生效後setInterval不會產生累積效應,即不會一會兒輸出三個2,而是隻會輸出一個2。

         

  • setTimeout(f, 0)
    • 若是指定時間爲0,即setTimeout(f, 0),那麼會馬上執行嗎?
      • 答案是不會。由於上一節說過,必需要等到當前腳本的同步任務,所有處理完之後,纔會執行setTimeout指定的回調函數f。
      • 也就是說,setTimeout(f, 0) 會在下一輪事件循環一開始就執行
        setTimeout(function () {
            console.log(1);
        }, 0);
        console.log(2);
        // 2
        // 1

        上面代碼先輸出2,再輸出1。由於2是同步任務,在本輪事件循環執行,而1是下一輪事件循環執行對象

 

    • setTimeout(f, 0)這種寫法的目的
      • 是,儘量早地執行 f(),
      • 可是並不能保證馬上就執行f()
      • 不一樣的瀏覽器有不一樣的實現
        • 以 Edge 瀏覽器爲例,會等到4毫秒以後運行。
        • 若是電腦正在使用電池供電,會等到16毫秒以後運行;
        • 若是網頁不在當前 Tab 頁,會推遲到1000毫秒(1秒)以後運行。
        • 這樣是爲了節省系統資源

 

      • 應用:
        • 能夠調整事件的發生順序
          • 好比,網頁開發中,某個事件先發生在子元素,而後冒泡到父元素,即子元素的事件回調函數,會早於父元素的事件回調函數觸發。
          • 若是,想讓父元素的事件回調函數先發生,就要用到 setTimeout(f, 0)
            // HTML 代碼以下
            // <input type="button" id="myButton" value="click">
            
            var input = document.getElementById('myButton');
            
            input.onclick = function A() {
                setTimeout(function B() {
                    input.value +=' input';
                }, 0)
            };
            
            document.body.onclick = function C() {
                input.value += ' body'
            };
          • 上面代碼在點擊按鈕後,先觸發回調函數 A ,而後觸發函數 C 。

          • 函數 A 中,setTimeout() 將函數 B() 推遲到下一輪事件循環執行,這樣就起到了,先觸發父元素的 回調函數 C() 的目的了

 

              • 用戶自定義的回調函數,一般在瀏覽器的默認動做以前觸發
                • 好比,用戶在輸入框輸入文本,keypress 事件 會在瀏覽器接收文本以前觸發。所以,下面的回調函數是達不到目的的
                  // HTML 代碼以下
                  // <input type="text" id="input-box">
                  
                  document.getElementById('input-box').onkeypress = function (event) {
                      this.value = this.value.toUpperCase();
                  }
                  // 上面代碼想在用戶每次輸入文本後,當即將字符轉爲大寫。
                  // 可是實際上,它只能將本次輸入前的字符轉爲大寫,
                  // 由於瀏覽器此時還沒接收到新的文本,因此 this.value 取不到最新輸入的那個字符
                • 解決:
                  document.getElementById('input-box').onkeypress = function() {
                      var self = this;
                      setTimeout(function() {
                           self.value = self.value.toUpperCase();
                      }, 0);
                  }

                  上面代碼將代碼放入setTimeout之中,就能使得它在瀏覽器接收到文本以後觸發

 

          • 因爲setTimeout(f, 0)實際上意味着,將任務放到瀏覽器最先可得的空閒時段執行,
          • 因此那些計算量大、耗時長的任務,經常會被放到幾個小部分,分別放到setTimeout(f, 0)裏面執行
            • var div = document.getElementsByTagName('div')[0];
              
              // 改變一個網頁元素的背景色 寫法一    會形成瀏覽器「堵塞」    由於 JavaScript 執行速度遠高於 DOM,會形成大量 DOM 操做「堆積」
              for (var i = 0xA00000; i < 0xFFFFFF; i++) {
                  div.style.backgroundColor = '#' + i.toString(16);
              }
              
              // 改變一個網頁元素的背景色 寫法二    的好處,解決了上面的問題
              var timer;
              var i=0x100000;
              
              function func() {
                  timer = setTimeout(func, 0);
                  div.style.backgroundColor = '#' + i.toString(16);
                  if (i++ == 0xFFFFFF){
                      clearTimeout(timer);
                  }
              }
              
              timer = setTimeout(func, 0);setTimeout(f, 0)

 

              • 代碼高亮的處理
                • 若是代碼塊很大,一次性處理,可能會對性能形成很大的壓力,
                • 那麼將其分紅一個個小塊,一次處理一塊,
                • 好比寫成 setTimeout(highlightNext, 50) 的樣子,性能壓力就會減輕
相關文章
相關標籤/搜索