深入js定時器setTimeout

對於瀏覽器內部,大部分操作都是異步的生成事件並添加到JavaScript 引擎線程的隊列中,然後由JavaScript 引擎線程進行調度執行。因此瀏覽器的很多事件都是和JavaScript相結合的,但是也有一些內部的限制。

首先我們非常確定JavaScript是單線程的,對於瀏覽器來說,一個窗體中只有一個JavaScript 引擎線程。而其他的行爲,如:渲染、下載等是由單獨的線程進行管理的,且具有不同的優先級。

異步事件

前面提到大多數事件都是異步的,觸發的時候就將回調函數添加到事件隊列。瀏覽器提供了一個內部的迴路,也就是之前所談到的Event Loop,由它來負責檢查隊列和處理事件、執行函數等。詳細可參考我的前一篇博文。而setTimeoutsetInterval也是將其需要執行的函數添加到事件隊列。

事實上,大多數交互和活動都得通過事件循環。

事件重疊

一些情況下,會有多個事件在同一時間附加到事件隊列裏。

比如,click事件就會產生兩個額外的事件:mousedownmouseup。其中,mouseupclick事件會同時被添加到事件隊列;而mousedown事件則很有可能會和另外一個事件重疊:focus

setTimeout(func, 0) 奇巧淫技

再一次解釋關於0ms的誤解:如果當前時鐘週期內執行隊列空閒,則立即執行該定時器,將回調函數加入到事件隊列;然後等待下一個時鐘週期,再執行該回調函數。不妨來看看下面的測試。

這段代碼在我的瀏覽器中執行結果如下:

在我本地的Nodejs環境中執行結果如下:

上面的這個測試只是想說明setTimeout(func, 0)定時任務的回調函數執行時間是有延遲的,而並不是所謂的立即執行。

因此,我們可以利用setTimeout(func, 0)來解決事件重疊所產生的負面效果,修正執行順序。

奇巧淫技之一:模擬瀏覽器的事件捕獲

衆所周知,瀏覽器的 DOM 事件都是採用冒泡的方式,只有個別瀏覽器是支持事件捕獲的。而在實際的開發過程中可能存在需要事件捕獲的需求,要求子元素的事件在父元素觸發之後才能觸發。爲了兼容各個瀏覽器,我們不能使用事件捕獲,而setTimeout(func, 0)在這個時候就很樂意幫忙了。

<input type="button" value="click" id="cbtn">
<div id="result"></div>
<script type="text/javascript">
  var cbtn = document.getElementById('cbtn')
    , result = document.getElementById('result');

  cbtn.onclick = function(e) {
    setTimeout(function() {
      result.innerHTML += 'input click, ';
    }, 0);
  };

  document.body.onclick = function(e) {
    result.innerHTML += 'body click -> ';
  };
</script>

點擊查看運行效果:

奇巧淫技之二:讓瀏覽器更好的工作

大多數情況下,我們可以在瀏覽器的默認行爲之前對事件進行處理,但是有時我們按照常規的思路去做的時候,往往事與願違。比如下面的例子。

<input type="text" id="wordInput">
<script type="text/javascript">
  var wordInput = document.getElementById('wordInput');
  wordInput.onkeypress = function(e) {
    this.value = this.value.toUpperCase();
  };
</script>

看似一個很簡單的需求:每輸入一個字符,就將其轉換爲大寫。但是上面的代碼完全沒有按照指示去做,不信你試試看:

如果沒有下一次輸入,文本框中的小寫字母永遠都不會轉換爲大寫。Why? 因爲瀏覽器在keypress事件處理的時候,還沒有將我們輸入的值添加到文本框。於是乎換一個事件來 handle 然後再處理吧,既然鍵按下的時候還木有值,那就等鍵彈起來之後再處理。

<input type="text" id="wordInput">
<script type="text/javascript">
  var wordInput = document.getElementById('wordInput');
  wordInput.onkeyup = function(e) {
    this.value = this.value.toUpperCase();
  };
</script>

運行試試吧。

大概似乎是可行了,可是仔細觀察就看出問題了。keyup事件觸發時,文本框已經具備完整的值了,但先是一個小寫的值,鍵完全釋放之後轉變爲大寫。這不科學…這太醜陋...

是時候關門放出setTimeout(func, 0)了。。。

<input type="text" id="wordInput">
<script type="text/javascript">
  var wordInput = document.getElementById('wordInput');
  wordInput.onkeypress = function(e) {
    var self = this;
    setTimeout(function() {
      self.value = self.value.toUpperCase();
    }, 0);
  };
</script>

已經完美了。keypress事件觸發時,將轉換大寫的操作添加到事件隊列,緊接着瀏覽器添加我們輸入的值,然後近乎 0 延遲的執行我們的轉換大寫操作函數。

上面兩個小案例只是冰山一角,so... 合理利用setTimeout(func, 0),明天更美好!

發佈了20 篇原創文章 · 獲贊 2 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章