假期這么快就結束了,其實對我來說沒什么影響,因為我一周才兩節課,對於課多的同學來說,我天天在休假,不要羡慕喲~ 但休假並不代表閑着,還是得苦逼的編代碼,唉。。一入程序深似海。。
不管學得多少,還是總結一些,還是一些小問題。當然也是很重要的,好! 廢話少說該入正題了。
上次提到異步,當時說,不知道是啥就去查漢語字典,但后來發現查了字典還是不會。回顧一下
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()仍只會被調用一次。
這個過程叫做函數節流,基本思想是:某個代碼不可以在沒有間斷的情況連續重復執行。今天先暫且消化這些。
哪不對,或者要補充,推薦的強烈歡迎。。。