setTimeout你知多少


假期這么快就結束了,其實對我來說沒什么影響,因為我一周才兩節課,對於課多的同學來說,我天天在休假,不要羡慕喲~  但休假並不代表閑着,還是得苦逼的編代碼,唉。。一入程序深似海。。

不管學得多少,還是總結一些,還是一些小問題。當然也是很重要的,好! 廢話少說該入正題了。

上次提到異步,當時說,不知道是啥就去查漢語字典,但后來發現查了字典還是不會。回顧一下

js哪些操作是異步的???setTimeout、setInterval、ajax、各種事件處理,才疏學淺,我就知道這些,誰還知道有哪些,勞煩告訴我,學習學習。

for(var i=0; i<5; i++){
   setTimeout(function(){
     console.log(i);
   },100);
}   //答案是: 5 5 5 5

for(var i=0; i<5; i++){
    (function(j){
       setTimeout(function(){
          console.log(j);
       },100);
    })(i);  
};   答案是 0 1 2 3 4

為什么是這個答案,重申一遍:作用域的關系。具體解釋作用域與執行環境無關,由定義時決定並一步一步往上查找。上述兩個例子 執行匿名函數時執行的是:

function(){
     console.log(i);
   }  而i等於多少? 我們從定義處查找 setTimeout中沒有i  在往上一層就到了全局中,此時i已經等於5  所以答案是全是5
function(){
     console.log(j);
   }  而j等於多少?  我們還是從定義處查找 setTimeout中沒有j  在往上一層就到了上一個形參為j的匿名函數,此時j是形參,在定義setTimeout中的函數時,j的形參依次被傳入實參i,依次為0,1,2,3,4  所以答案是全是0,1,2,3,4

換湯不換葯,找個例子實驗一下:

[1,2,3,4,5].forEach(function(elem){
    setTimeout(function(){console.log(elem)}, 200);
})  答案是多少??  5,5,5,5??  1,1,1,1??  1,2,3,4,5還是???  答案是1,2,3,4,5  如果錯了的話,再把前面的例子,文字看看。 

繼續,再來一道:

for(var i=0; i<5; i++){
   (function(j){
      setTimeout(function(){
          console.log(j);
      }, Math.random()*1000);
   })(i);
} //這個答案是什么呢??  是0,1,2,3,4還是什么???   好好想想。

根據前面的分析,先找定義處 依次往上查找,到function(j)這個函數時,已經把實參i傳進來了,所以答案是0,1,2,3,4  yes or no?? 答案是錯誤的。為什么?

實參i確實把值傳進來了,該段代碼就等價於

setTimeout(function(){
          console.log(0);
      }, Math.random()*1000);

setTimeout(function(){
          console.log(1);
      }, Math.random()*1000);

setTimeout(function(){
          console.log(2);
      }, Math.random()*1000);

setTimeout(function(){
          console.log(3);
      }, Math.random()*1000);

setTimeout(function(){
          console.log(4);
      }, Math.random()*1000);   

此時只看這段代碼 答案是多少??? 大家肯定會說是亂序的,跟 (Math.random()*1000) 值有關,yes 你答對了。 所以上面那個答案是亂序的0,1,2,3,4

那么,下面那個代碼呢?

setTimeout(function(){
   console.log(15);
},100);
setTimeout(function(){
   console.log(5);
},200);  

很多人肯定會說,這還用說嗎? 不用想都知道是15, 5。對,但是就這段代碼而言,這個15,5   從等待到執行(此處執行時間忽略不計)一共是花了300ms還是200ms呢? 答案是200ms,為什么?剛剛開頭就說過,setTimeout()函數是異步的,異步有個的特點就是並發性,在同時定義這兩個函數時,他們同時在等待,放入到消息隊列中,所以100ms后第一個函數放入時,第二個函數已經等了100ms,所以兩個函數一共等了200ms。 總之一句話:異步具有並發性,與順序無關(時間相同或者相近的情況下有關),與時間的快慢有關,請記住它。

這里還要提的是:關於定時器中的時間,指的是何時將定時器的代碼添加到隊列中,而不是何時實際執行代碼,只能表示它會盡快執行。

如 :

document.onclick = function(){
  setTimeout(function(){
          console.log(34);
      },250); 
};  //如果onclick事件處理程序執行了300ms 那么定時器代碼至少要在定時器設置后的300ms才會被執行,也就是34至少要在300ms后輸出。

 大家馬上就想到,如果是這樣的話,setInterval()就會出現一種情況:在代碼再次被添加到隊列之前沒完成執行,導致定時器代碼連續運行好幾次,沒有停頓。幸好,js引擎夠聰明,能避免這個問題,如何避免?當使用setInterval()時,僅當沒有該定時器的任何其他代碼實例時,才將定時器代碼添加到隊列中,這樣確保了定時器代碼加入到隊列中的最小時間間隔為指定間隔,注意是添加到隊列中的最小時間間隔而不是執行。但是。。這個規則有兩個問題:1.某些間隔會被跳過,2.多個定時器的代碼執行之間的間隔可能會比預期的少。舉個例子:

某個onclick事件處理程序使用setInterval()設置了一個200ms間隔的重復定時器,如果事件程序花了300ms多一點的時間完成,定時器也花差不多的時間,就會出現上述兩個問題。

如圖:   此圖來自《js高級程序設計》這本書強烈推薦閱讀。。。

我們分析一下:在5ms時創建了間隔為200ms的定時器,第一個定時器在205ms后被添加到隊列中,但直到onclik執行完才執行,執行時,在405ms處第二個定時器又被添加到隊列中,在605ms第三個定時器要添加到隊列中,但此時第二個定時器還沒被執行,所以第三個不會被添加,同時在第一個定時器執行完之后第二個立即執行,所以第一個定時器和第二個定時器執行的間隔小於200ms,其實此處就是從第一個執行結束到第二個開始執行沒有間隔。

有人可能想到這樣的話js引擎並沒有解決定時器代碼連續運行問題,確實,但其實js引擎這種做法(在僅當沒有該定時器的任何其他代碼實例時,才將定時器代碼添加到隊列中),減少了連續的次數,不至於堆積太多。

為了避免這2個缺點,可以使用如下模式使用鏈式setTimeout()調用。

setTimeout(function(){
   //處理中
   setTimeout(arguments.callee,interval); //arguments.callee  獲取對當前函數執行的引用。  

//此處把需要處理的代碼寫在前面 有一個好處是 :下一個定時器一定是在前一個將要結束(此處可以之直接視為結束)才定義
}, interval);

每次函數執行時創建一個新的定時器,這樣的好處:在前一個定時器代碼執行完之前,不會向隊列插入新的定時器代碼,確保不會有任何缺失的間隔,而且,它可以保證在下一次定時器代碼執行之前,至少要等待指定的間隔,避免了連續的運行。詳細請看《js高級程序設計》這本書。

 關於setTimeout()函數還有一點就是:

大家都知道DOM比非DOM交互要更多的內存和CPU時間,如果連續進行過多的DOM相關操作可能會導致瀏覽器掛起甚至崩潰。如resize事件,為了繞開這個問題我們可以使用setTimeout();

模式如下:

var processor = {
    timeoutId: null,
    performProcessing: function(){
      //實際執行代碼 
   },
   process: function(){
clearTimeout(this.timeoutId);
var that = this; //保存this,因為setTimeout()中用到的函數環境總是window
this.timeoutId = setTimeout(function(){ //timeoutId用來保存本次setTimeout的id以便下一次調用時清除
that.performProcessing();
}, 100);
}
};

processor.process();

時間間隔設為100ms,表示最后一次調用process()之后至少100ms后才會被調用performProcessing(),如果100ms之內調用了process()共20次,performProcessing()仍只會被調用一次。因為,在100ms之內定時器都沒開始執行,調用process()只會清除前一次的,最后只剩下最后一次setTimeout()。也就是說performProcessing()仍只會被調用一次。

這個過程叫做函數節流,基本思想是:某個代碼不可以在沒有間斷的情況連續重復執行。今天先暫且消化這些。

哪不對,或者要補充,推薦的強烈歡迎。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM