JavaScript -- 定時器

介紹

JavaScript 提供了兩個方法供我們設置一個定時器,它們分別是 setTimeout()setInterval()。這兩種方法的使用方法是相同的,都接收兩個參數(shù),第一個參數(shù)是一個回調函數(shù),第二個參數(shù)是延遲的毫秒數(shù)。這就造成了一種 JavaScript 是多線程語言的假象,因為相同的功能在 Java 中稱之為 sleep() 或者 Lock.lock(),它們的作用都是堵塞當前線程,為其他線程騰出處理器資源。

實際上 JavaScript 是運行在單線程環(huán)境中的,它擁有一個事件處理隊列,所有要處理的事件都會被放置在這個隊列中排隊等待執(zhí)行。這樣的話就出現(xiàn)了一個問題,瀏覽器并不能保證我們的代碼會在指定的時間內執(zhí)行。

舉個例子來說,如果一個事件的執(zhí)行時間非常長,那么在這個時間的執(zhí)行過程中,我們點擊頁面上的任何按鈕或者其他可點擊控件,都無法得到回饋,因為我們的點擊事件正在隊列中排隊執(zhí)行。

從上面的介紹中,我們明白了一個道理,那就是不能讓一個事件處理時間過長,否則就會導致用戶無法與頁面進行體驗良好的交互。所以,現(xiàn)在有很多技巧用于處理耗時操作,比如函數(shù)節(jié)流和分塊處理,接下來我會講解這些技巧。

兩種方法的比較

setTimeout() 的作用是在指定時間內執(zhí)行一個任務。setInterval() 的作用是以指定時間周期性的執(zhí)行任務。

按理說,這兩種方法分工明確,我們應該根據(jù)自身的需要選擇使用 setTimeout() 或者 setInterval(),但是目前的最佳實踐卻是始終使用 setTimeout(),即在應該使用 setTimeout() 的時候使用 setTimeout(), 在應該使用 setInterval() 的時候用 setTimeout() 去替代。

原因就是使用 setInterval() 的時候會出現(xiàn)間隔跳過問題。比如我們設置了一個 setInterval(callback, 10),如果這個 callback 的執(zhí)行時間是 20ms,那么就會出現(xiàn)無間隔連續(xù)執(zhí)行 callback 的情況,不過 JavaScript 引擎處理的過程卻不和我們想象的一樣,如果當前事件隊列中已經(jīng)有定時器代碼實例了,它就不會再放一個相同的定時器進去。這也就導致一部分定時器會被跳過的問題。

下面是利用 setTimeout() 代替 setInterval() 的例子。

        function callback() {
            console.log("Hello World!");
        }
        setInterval(callback, 10);

        // After
        function callback() {
            console.log("Hello World");
            setTimeout(callback, 10);
        }
        setTimeout(callback, 10);

使用了 setTimeout() 之后,可以保證在一個定時器任務執(zhí)行之后才會再次將一個定時器插入隊列,不會有丟失間隔的問題。


高級技巧

  • 分塊處理

導致腳本長時間運行的兩個主要原因就是過深過長的嵌套函數(shù)調用和包含大量處理過程的循環(huán)。對于后一個問題,我們可以對循環(huán)進行切割,分時處理,騰出時間為其他事件進行服務。

        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];
        function printValue(i) {
            console.log(i);
        }

        chunk(data, printValue);

可見,在以上代碼中我們以 100ms 的間隔去執(zhí)行打印事件,這樣的話在間隔的過程中,瀏覽器就能很好的處理與用戶的交互。在執(zhí)行耗時任務時,這一技巧十分重要,因為網(wǎng)頁的明顯卡頓會讓你的用戶離你而去。

  • 函數(shù)節(jié)流
    因為 JavaScript 是在瀏覽器中執(zhí)行的,所以它的限制非常大,這也就迫使我們去考慮代碼的性能以及資源的利用問題。函數(shù)節(jié)流的思想就是,某些代碼不可以在沒有間隔的情況下連續(xù)執(zhí)行。舉個例子來說,如果用戶瘋狂的點擊頁面中的一個按鈕,頻率非常之高,這個時候就不能按照用戶點擊的次數(shù)去調用處理程序。難道用戶一秒點擊 20 次按鈕我們還要重復的執(zhí)行 20 次處理程序嗎?這是完全沒有必要的,所以我們可以采取 setTimeout() 讓用戶請求結束后一段時間再去執(zhí)行。

          var clickButton = document.getElementById("click");
    
          function throttle(method, context) {
              clearTimeout(method.tId);
    
              method.tId = setTimeout(function() {
                  method.call(context);
              },2000);
          }
    
          function print() {
              console.log("You click the button!");
          }
    
          clickButton.onclick = function() {
              throttle(print);
          }    
    

以上代碼就保證了在 2s 之內無論你點擊了多少次按鈕,事件處理函數(shù)只會執(zhí)行一次,當然設置 2s 只是試驗性的,在實際開發(fā)過程中,2s 可能太長了,需要改成一個較小的值,比如說 100ms 。

  • 中央定時器控制
    如果我們同時創(chuàng)建了大量的定時器,將會在瀏覽器中增加垃圾回收任務發(fā)生的可能性。所以為了避免我們的定時器被當做垃圾回收掉,可以使用中央定時器控制的技術。下面是中央定時器控制的一些特點:
  1. 每個頁面在同一時間只需要運行一個定時器。

  2. 可以根據(jù)需要暫停和恢復定時器。

  3. 刪除回調函數(shù)的過程變得簡單。

      var timers = {
        timerId: 0,
        timers: [],
    
        add: function(fn) {
            this.timers.push(fn);
        },
    
        start: function() {
            if(this.timerId) {
                return;
            }
    
            (function runNext() {
                if(timers.timers.length > 0) {
                    for(var i = 0; i < timers.timers.length; i++) {
                        if(timers.timers[i]() == false) {
                            timers.timers.splice(i, 1);
                            i--;
                        }
                    }
                    timers.timerId = setTimeout(runNext,0);
                }
            })();
        },
    
        stop: function() {
            clearTimeout(this.timerId);
            this.timerId = 0;
        }
     };
    

上述代碼就創(chuàng)建了一個簡單的中央定時器,首先我們在 timers 中定義了 timerId 和 timers, timerId用于保存定時器的 id,用于控制定時器的開啟和關閉,timers 用于保存要執(zhí)行的函數(shù)。

最主要的還是 start 函數(shù),首先對 timerId 進行判斷,如果還沒有啟動定時器,則立即啟動一個,如果已經(jīng)有定時器啟動了,則直接返回,用于確保整個環(huán)境中只存在一個定時器實例。每次執(zhí)行定時器實例,都會執(zhí)行一次保存在 timers 隊列中的函數(shù),如果執(zhí)行的函數(shù)返回結果為 false, 就會將其從隊列中刪除,這樣下次就不會再執(zhí)行該函數(shù)了。


End!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 一、什么是定時器 JS提供了一些原生方法來實現(xiàn)延時去執(zhí)行某一段代碼,下面來簡單介紹一下 setTimeout: 設...
    SSSSSSH閱讀 1,008評論 1 50
  • 1、下面這段代碼輸出結果是? 為什么? JavaScript 所有任務可以分成兩種,一種是同步任務(synchro...
    zh_yang閱讀 219評論 0 0
  • 9.26-9.30 第8章 馴服線程和定時器 定時器可以在js中使用,但它不是js的一項功能,如果我們在非瀏覽器環(huán)...
    如201608閱讀 652評論 0 2
  • 每天一句:如果,感到此時的自己很辛苦,那告訴自己:容易走的都是下坡路!堅持住,因為你正在走上坡路,走過去,你就一定...
    EndEvent閱讀 362評論 0 0
  • 先上一組圖,感受一下: 如此眼花繚亂的盤盤碗碗和盛在其中的美食,讓我口水飛流三千尺。 這是英國的邁克爾·徐為男友馬...
    詩涵Ady閱讀 421評論 0 0

友情鏈接更多精彩內容