起因源於幾道前端筆試題:
var fuc = [1,2,3]; for(var i in fuc){
setTimeout(function(){console.log(fuc[i])},0); console.log(fuc[i]); }
for(var i = 0; i < 3; i ++) {
(function (x) {
setTimeout(function () {
console.log(i);
});
console.log(i);
})(i);
}
問:控制台會如何打印?
chrome打印結果如下:
雖然setTimeout函數在每次循環的開始就調用了,但是卻被放到循環結束才執行,循環結束,i=3,接連打印了3次3。
這里涉及到javascript單線程執行的問題:javascript在瀏覽器中是單線程執行的,必須在完成當前任務后才執行隊列中的下一個任務。
另外,對於javascript還維護着一個setTimeout隊列,未執行的setTimeout任務就按出現的順序放到setTimeout隊列,等待普通的任務隊列中的任務執行完才開始按順序執行積累在setTimeout中的任務。
所以在這個問題里,會先打印1 2 3,而將setTimeout任務放到setTimeout任務隊列,等循環中的打印任務執行完了,才開始執行setTimeout隊列中的函數,所以在最后會接着打印3次3。
由此,可以知道雖然設置為0秒后執行任務,實際上是大於0秒才執行的。可是這有什么用呢?
用處就在於我們可以改變任務的執行順序!因為瀏覽器會在執行完當前任務隊列中的任務,再執行setTimeout隊列中積累的的任務。
通過設置任務在延遲到0s后執行,就能改變任務執行的先后順序,延遲該任務發生,使之異步執行。
網上有個比較有意思的案例:
<!DOCTYPE html> <html lang="zh-cmn-Hans"> <head> <title> 圖 </title> <meta charset="utf-8"> </head> <body> <p> <input type="text" id="input" value=""/> <span id="preview"></span> </p> </body> <script type="text/javascript"> (function(){ function $(id){ return document.getElementById(id); } $('input').onkeypress = function(){ $('preview').innerHTML = this.value; } })();
</script> </html>
這個keypress函數原意是監聽到用戶輸入字符串就將其完整的顯示出來,但是奇怪的是最后一個字符串總是沒能顯示出來:
,
但是只要改下onkeypress函數就好:
$('input').onkeypress = function(){ setTimeout(function(){$('preview').innerHTML = $('input').value;},0); }
將onkeypress里面的事件延遲到瀏覽器更新相關DOM元素的狀態之后執行,這是就能顯示出所有字符串了,如下:
setTimeout()是用來改變任務執行順序的,難道就沒有其他代替的方法了嗎?答案是有的。
將監聽keypress事件改為監聽keyup事件,一樣能達到同樣的效果:
$('input').onkeyup = function(){ $('preview').innerHTML = $('input').value; }
這里跟key事件的執行順序有關:
(function(){ function $(id){ return document.getElementById(id); } function log(a){ console.log(a); } $('input').onkeypress = function(){ log("keypress: "+this.value); } $('input').onkeyup = function(){ log("keyup: "+this.value); } $('input').onkeydown=function(){ log("keydown: "+this.value); } })();
當用鍵盤輸入1后,控制台打印結果如下:
也就是先執行keydown事件,再執行keypress事件,執行keypress事件之后改變dom元素的狀態(這里是input的value改變了),再執行keyup。
這也解釋了為什么通過監聽keyup事件就能正確且及時的打印出input的值。
而keypress事件發生時,dom元素的狀態還未改變,keypress事件之后dom元素的狀態才發生改變,通過setTimeout延遲執行就能達到期望的結果了。
測試過chrome,firefox,ie表現一致。
再來看看另一個例子,稍微有些改動,來自http://blog.csdn.net/lsk_jd/article/details/6080772:
<!DOCTYPE html> <html lang="zh-cmn-Hans"> <head> <meta name="generator" content="HTML Tidy for HTML5 (experimental) for Windows https://github.com/w3c/tidy-html5/tree/c63cc39"> <title> 圖 </title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta charset="utf-8"> <meta name="Description" content="SUperman"> <style type="text/css"> </style> </head> <body> <h2>1、未使用 <code>setTimeout</code></h2> <button id="makeinput">生成 input</button> <p id="inpwrapper"></p> <h2>2、使用 <code>setTimeout</code></h2> <button id="makeinput2">生成 input</button></h2> <p id="inpwrapper2"></p> </body> <script type="text/javascript"> (function(){ function get(id){ return document.getElementById(id); } function log(a){ console.log(a) }
window.onload = function(){ get('makeinput').onmousedown=function(){ var input = document.createElement('input'); input.setAttribute('type', 'text'); input.setAttribute('value', 'test1'); get('inpwrapper').appendChild(input); input.focus(); } get('makeinput2').onmousedown = function(){ var input = document.createElement('input'); input.setAttribute('type', 'text'); input.setAttribute('value', 'test1'); get('inpwrapper2').appendChild(input); setTimeout(function(){input.focus();},0); } } })(); </script> </html>
該例子中,未使用setTimeout生成的input沒有獲得focus,而使用了setTimeout的input可以獲得focus,這也是和setTimeout改變任務執行順序有關。
可是生成的input為什么不會focus呢?是這個focus沒執行嗎,通過給focus綁定一個事件就可以知道事實是怎樣:
get('makeinput').onmousedown=function(){ var input = document.createElement('input'); input.setAttribute('type', 'text'); input.setAttribute('value', 'test1'); get('inpwrapper').appendChild(input); input.onfocus=function(){ //給生成的input綁定focus事件 log("focused"); } input.focus(); }
結果發現控制台有打印"focused"的,進一步猜測未使用setTimeout生成的input獲取了focus又失去focus,改下代碼看看mouse事件和focus事件的執行順序:
get('makeinput').onmousedown=function(){ var input = document.createElement('input'); input.setAttribute('type', 'text'); input.setAttribute('value', 'test1'); get('inpwrapper').appendChild(input); input.onfocus=function(){ log("focused"); } input.focus(); log("down"); } get('makeinput').onfocus = function(){ log("focus"); } get('makeinput').onclick = function(){ log("click"); } get('makeinput').onmouseup=function(){ log("up"); }
結果如下:
可見先執行mousedown事件,然后生成的input獲得focus,接着按鈕獲得focus,接着執行mouseup事件,再執行click事件,和key的3個事件的執行順序有些不同。
看到這里恍然大悟,這個生成的input確實是獲得了focus,但是隨之失去focus,因為按鈕的mousedown事件緊跟着按鈕的focus事件,被按鈕奪取了focus。
將 input.focus(); 改為 setTimeout(function(){input.focus();},0);得到結果為:
也就是通過延遲執行input獲取focus事件,最終就是生成的input獲取了focus。
這樣的話,通過綁定mouseup,click事件同樣能使生成的input奪取按鈕的focus事件,事實證明確實如此,就不貼代碼上來了。
如果綁定focus事件會怎樣呢?這一次,chrome和ie站在了統一戰線,都是生成一個獲取了focus的input,但有趣的是firefox居然生成了2個。
不過ie反應也不算正常,綁定mousedown時不使用setTimeout的輸出是這樣的orz:
也就是focus事件好像不執行了,由於沒有執行按鈕的focus事件,生成的input是獲得focus的,也就是不需要延遲執行函數,生成的input也能獲取focus。
趕緊將input.focus()注釋掉,發現打印出"focused"的地方替換成了"focus",將生成input且獲取focus的代碼綁定到其他mouse事件和focus事件得到的結果比較復雜,就暫且不做深究,但是可以知道按鈕都能執行focus事件,除了綁定mousedown事件忽略了foucs事件。
原因不詳,應該是和各瀏覽器對focus的具體實現有差異吧。看到的朋友若知道什么原因,歡迎告知。
不過折騰了這么久,敢肯定setTimeout確實是實現異步執行的利器。這次對setTimeout的探討也沒有很深入,不過同時也弄清楚了key事件、mouse事件和focus事件的執行順序。
本人正在學習和摸索中,如有錯誤,歡迎指正!
-------------------------------轉載注明出處^_^: http://www.cnblogs.com/suspiderweb/