由 John Resig 的 How JavaScript Timers Work 可以知道,現(xiàn)有的 JavaScript 引擎是單線程處理任務(wù)的。它把任務(wù)放到隊(duì)列中,不會(huì)同步去執(zhí)行,必須在完成一個(gè)任務(wù)后才開(kāi)始另外一個(gè)任務(wù)。
讓我們看看我之前的文章:JavaScript的9個(gè)陷阱及評(píng)點(diǎn),在第 9 點(diǎn) Focus Pocus 中提到的問(wèn)題。原作者對(duì)這個(gè)認(rèn)識(shí)有所偏差,其實(shí)不只是 IE 的問(wèn)題,而是現(xiàn)有 JavaScript 引擎對(duì)于線程實(shí)現(xiàn)的問(wèn)題(關(guān)于線程,我的概念其實(shí)不多,如果不對(duì),希望讀者多多指教)。我們通過(guò)一個(gè)例子來(lái)說(shuō)明,請(qǐng)?jiān)L問(wèn) http://realazy.org/lab/settimeout.html. 我們來(lái)看 1 和 2。如果你能看看源代碼,會(huì)發(fā)現(xiàn)我們的任務(wù)很簡(jiǎn)單,就是給文檔增加一個(gè) input
文本框,并聚焦和選中。請(qǐng)現(xiàn)在分別點(diǎn)擊一下,可以看到,1 并沒(méi)有能夠聚焦和選中,而 2 可以。它們之間的區(qū)別在于,在執(zhí)行
input.focus();input.select();
時(shí), 2 多了一個(gè)延遲時(shí)間為 0 的 setTimeout
的外圍函數(shù),即:
setTimeout(function(){input.focus();input.select();}, 0);
按照 JavaScript: The Definitive Guide 5th 的 14.1 所說(shuō):
在實(shí)踐中,
setTimeout
會(huì)在其完成當(dāng)前任何延宕事件的事件處理器的執(zhí)行,以及完成文檔當(dāng)前狀態(tài)更新后,告訴瀏覽器去啟用setTimeout
內(nèi)注冊(cè)的函數(shù)。
其實(shí),這是一個(gè)把需要執(zhí)行的任務(wù)從隊(duì)列中跳脫的技巧?;氐角懊娴睦樱琂avaScript 引擎在執(zhí)行 onkeypress
時(shí),由于沒(méi)有多線程的同步執(zhí)行,不可能同時(shí)去處理剛創(chuàng)建元素的 focus
和 select
事件,由于這兩個(gè)事件都不在隊(duì)列中,在完成 onkeypress
后,JavaScript 引擎已經(jīng)丟棄了這兩個(gè)事件,正如你看到的例子 1 的情況。而在例子 2 中,由于setTimeout
可以把任務(wù)從某個(gè)隊(duì)列中跳脫成為新隊(duì)列,因而能夠得到期望的結(jié)果。
這才是延遲事件為 0 的setTimeout
的真正目的。在此,你可以看看例子 3,它的任務(wù)是實(shí)時(shí)更新輸入的文本,現(xiàn)在請(qǐng)?jiān)囋嚕銜?huì)發(fā)現(xiàn)預(yù)覽區(qū)域總是落后一拍,比如你輸 a, 預(yù)覽區(qū)并沒(méi)有出現(xiàn) a, 在緊接輸入 b 時(shí), a 才不慌不忙地出現(xiàn)。其實(shí)我們是有辦法讓預(yù)覽區(qū)跟輸入框同步地,在此我沒(méi)有給出答案,因?yàn)樯厦嫠f(shuō)的,就是解決思路,try it yourself!
March 30th, 2008 at 01:56
get(’input’).onkeypress = function(){
setTimeout(function() {get(’preview’).innerHTML = get(’input’).value;}, 0)
}
March 30th, 2008 at 11:51
JavaScript 引擎并不會(huì)丟棄事件,在你的例子中
input.focus();
input.select();
已經(jīng)被執(zhí)行。并且input不能獲取焦點(diǎn)的解決方法不一定是使用setTimeout,在Firefox和Safari中,只要使用return false取消默認(rèn)行為就能夠達(dá)到目的。另外Opera不需要任何技巧直接能夠正確執(zhí)行以上兩行代碼,我想可能是因?yàn)镺pera沒(méi)有在button的mousedown事件上設(shè)置默認(rèn)行為。
不過(guò)return false沒(méi)有解決IE的這個(gè)問(wèn)題,IE確實(shí)是執(zhí)行focus和select方法,input也確實(shí)得到了焦點(diǎn),但是似乎select的默認(rèn)行為沒(méi)有被執(zhí)行,所以IE中看不到input中的文本被選中。
March 30th, 2008 at 12:49
我剛才說(shuō)的IE的情況看來(lái)是IE的mousedown事件下的一個(gè)bug,使用onclick事件就沒(méi)有這個(gè)問(wèn)題了。
March 31st, 2008 at 10:38
@Lunatic Sun 實(shí)際上,從一個(gè)不是很專業(yè)的角度來(lái)說(shuō),click = mousedown + mouseup. 雖然我沒(méi)有深入研究,但可以這么假定:mouswdown 時(shí)執(zhí)行創(chuàng)建 dom 事件,而 mouseup 時(shí)執(zhí)行 focus 和 select 的事件,因而沒(méi)有問(wèn)題。這也是在追求速度時(shí),推薦使用 mousedown 替換 click 的原因,只不過(guò) mousedown 不像 click 一樣,click 時(shí),用戶可以不釋放鼠標(biāo),從而有反悔的機(jī)會(huì)。
March 31st, 2008 at 10:55
@realazy - 從用戶體驗(yàn)的角度,我推薦使用click事件的原因有兩個(gè):
1 我們應(yīng)當(dāng)讓用戶有反悔的機(jī)會(huì);
2 在button的mousedown事件中使用移開(kāi)焦點(diǎn)的代碼input.focus()會(huì)使瀏覽器本身的繪畫(huà)button被按下和彈起的那種效果消失。
April 1st, 2008 at 14:30
應(yīng)該是這樣,input.focus();input.select(); 都執(zhí)行成功。 但由于采用了onmousedown事件,mousedown后隨后觸發(fā)mouseup于是焦點(diǎn)立即移回到button。這樣雖然input.select(); 已執(zhí)行成功但確看不出來(lái)
April 4th, 2008 at 21:15
我前一段時(shí)間在寫(xiě)js代碼時(shí), 經(jīng)常出現(xiàn)在firefox ie7 opera等瀏覽器工作正常的代碼, 在ie6下失效的情況。 后來(lái)我發(fā)現(xiàn)失效部分的代碼用一個(gè)setTimeout函數(shù)延時(shí)一下就可以正常工作了, 我通常都是設(shè)1ms的延時(shí), 沒(méi)試過(guò)0, 一直以為是ie6的效率問(wèn)題。 看過(guò)這篇文章比較受啟發(fā)。 回去試一下, 估計(jì)正好能解決因?yàn)樵O(shè)了1ms延時(shí)對(duì)后續(xù)部分代碼影響的問(wèn)題了:)
April 5th, 2008 at 05:44
……問(wèn)題不是阻斷罷,而是onkeydown/onkeypress的時(shí)候,根本就還沒(méi)有完成輸入罷, value本來(lái)就沒(méi)改變,同樣, onmousedown的時(shí)候點(diǎn)擊事件還未完成,select和focus實(shí)際上執(zhí)行過(guò)了,只不過(guò)又被點(diǎn)擊事件取消了而已……
你可以把那個(gè)測(cè)試頁(yè)面的代碼改成這樣試試:
get(’input’).onkeydown = function(){
get(’preview’).innerHTML += this.value+’1′;
var me = this;
setTimeout(function(){
get(’preview’).innerHTML += me.value+”2″;
}, 0);
};
get(’input’).onkeyup = function(){
get(’preview’).innerHTML += this.value+’3′;
var me = this;
setTimeout(function(){
get(’preview’).innerHTML += me.value+”4″;
}, 0);
};
get(’input’).onkeypress = function(){
get(’preview’).innerHTML += this.value+”5″;
var me = this;
setTimeout(function(){
get(’preview’).innerHTML += me.value+”6″;
}, 0);
};
April 5th, 2008 at 10:52
@dexter_yy 或許我所舉的例子不是很好。你所說(shuō)的 onkeydown/onkeypress的時(shí)候,根本就還沒(méi)有完成輸入,我是這樣認(rèn)為的:正是因?yàn)?正在輸入 這個(gè)進(jìn)程阻斷了其他事件,因此才需要
setTimeout
來(lái)為被隔斷的進(jìn)程重新排程。p.s. 你的 blog 很棒!
April 9th, 2008 at 18:09
我認(rèn)為在第3個(gè)例子中,我們定義了對(duì)onkeypress事件的處理函數(shù)(即在span中顯示input內(nèi)的值),而瀏覽器自身也有一個(gè)對(duì)onkeypress事件的處理函數(shù)(即在input框中顯示你輸入的那個(gè)值)。我認(rèn)為瀏覽器把這兩個(gè)函數(shù)放在了一個(gè)對(duì)onkeypress事件監(jiān)聽(tīng)的隊(duì)列里,并且用戶定義的函數(shù)先運(yùn)行了,瀏覽器自己的函數(shù)后運(yùn)行??梢院?jiǎn)單的修改一下第3個(gè)例子就能看出這個(gè)效果。
get(’input’).onkeypress = function(){
alert(this.value);
}
注意當(dāng)alert出現(xiàn)的時(shí)候input框中并沒(méi)有值,當(dāng)點(diǎn)擊alert的確定后,input框中出現(xiàn)了輸入的值。
在這里使用setTimeout其實(shí)就是推遲了用戶定義的那個(gè)函數(shù)的運(yùn)行時(shí)間,瀏覽器會(huì)在處理好所有onkeypress事件監(jiān)聽(tīng)函數(shù)后運(yùn)行setTimeout中的內(nèi)容。
April 11th, 2008 at 10:49
我之前對(duì)click的理解也是click=down+up, 這些實(shí)例把down或者press換成up就行了,理由或許正是LZ講的 追求速度時(shí)推薦使用up而不是down,這樣同樣允許用戶反悔而且會(huì)保留按鈕的動(dòng)畫(huà)效果~
有個(gè)實(shí)例是 http://www.clickclickclick.com和www.diandiandian.net的點(diǎn)擊效果,赫赫
April 11th, 2008 at 21:19
1) Javascript不會(huì)丟棄事件, 只要在select()語(yǔ)句后再加上一句 alert(’after select’)方法,執(zhí)行就可以看到此時(shí),input已經(jīng)被選中;
2) JS引擎首先執(zhí)行用戶自定義事件處理方法,然后才執(zhí)行默認(rèn)行為;
3) JS引擎碰到setTimeout方法會(huì)將其放入隊(duì)列等待, 并”跳過(guò)”其程序塊而繼續(xù)執(zhí)行其所在方法的后續(xù)代碼,執(zhí)行完成之后才從隊(duì)列中調(diào)用setTimeout~
因此,以下代碼同樣可以解決問(wèn)題:
get(’makeinput’).onmousedown = function(){
setTimeout(function(){
var input = document.createElement(’input’);
input.setAttribute(’type’, ‘text’);
input.setAttribute(’value’, ‘test1′);
get(’inpwrapper’).appendChild(input);
input.focus();
input.select();
},0);
}
1> 執(zhí)行onmousedown程序體;
2> 跳過(guò)setTimeout方法,沒(méi)有其他代碼;
3> 執(zhí)行setTimeout方法,沒(méi)有問(wèn)題;
ps: lz有必要?jiǎng)h除留言么? …………….
http://lonevan.blogbus.com/logs/18841965.html 看您的文章做的些個(gè)小實(shí)驗(yàn)
April 18th, 2008 at 11:10
realazy 看您的第三個(gè)例子。
可以去嘗試的輸入幾個(gè)字符(中文字也試下)
試的時(shí)候:
1.backspace下 ,delete下,情況還是不同的。
2.shift ctrl alt等按鍵。
可能會(huì)有出乎您的預(yù)料。
js里內(nèi)存是怎么回事,
貌似不像上邊大家所說(shuō)的那樣。
April 18th, 2008 at 16:35
樓上說(shuō)的應(yīng)該是在ie6測(cè)試的吧(7,8沒(méi)試過(guò))
您在firefox下再測(cè)試下backspace,delete,shift,ctrl,alt
順便再加上 up,down,left,right 等等非字母數(shù)字鍵
瀏覽器解釋的差異造成的,不過(guò)ie可非w3c~ ^^
April 19th, 2008 at 23:40
樓上諸位都說(shuō)了很多了,其實(shí)input.focus();input.select();都執(zhí)行了,只是被onmouseup影響了。
第三個(gè)例子,我認(rèn)為這種效果不應(yīng)該用onkeypress,因?yàn)檫@個(gè)時(shí)候還沒(méi)有完成輸入動(dòng)作,改成onkeyup就不存在延遲了,
采用setTimeout,確實(shí)可以不改動(dòng)原有的事件,達(dá)到效果,理由博主已經(jīng)說(shuō)了,“在實(shí)踐中,setTimeout 會(huì)在其完成當(dāng)前任何延宕事件的事件處理器的執(zhí)行,以及完成文檔當(dāng)前狀態(tài)更新后,告訴瀏覽器去啟用 setTimeout 內(nèi)注冊(cè)的函數(shù)?!?/p>
May 7th, 2008 at 23:40
確實(shí) setTimeout 的問(wèn)題是存在的,只是 realazy 舉的這個(gè)例子不太好。
最近就在 ajax 創(chuàng)建 dom 加載內(nèi)容的時(shí)候遇到了這樣的問(wèn)題,這個(gè)問(wèn)題只在 ie 下存在,firefox 沒(méi)有問(wèn)題。
May 9th, 2008 at 19:03
1. 并不是所有瀏覽器都支持setTimeout延時(shí)為0。而且它們的定時(shí)排程也各有差異。
2. 你的例子其實(shí)有問(wèn)題,所以你后面的論述統(tǒng)統(tǒng)都白搭了,因?yàn)槟阏门龅搅薎E的bug(Lunatic Sun的判斷是對(duì)的,只是這個(gè)bug的本質(zhì)不是那么容易認(rèn)識(shí)到),所以什么問(wèn)題也證明不了。IE的HTML focus等實(shí)際是被轉(zhuǎn)換為windows控件的focus,所以這里存在兩種不同層次的focus機(jī)制。理想上,HTML的focus應(yīng)該被同步轉(zhuǎn)換為windows控件的focus,然而IE可憐的代碼導(dǎo)致這種轉(zhuǎn)換存在某些不同步的bug。你不幸就遇到了。實(shí)際上,即使沒(méi)有使用setTimeout,調(diào)用focus()之后,HTML focus確實(shí)已經(jīng)到了新生成的input中,這一點(diǎn)你可以通過(guò)document.activeElement來(lái)驗(yàn)證。然而,由于mousedown事件默認(rèn)會(huì)獲得控件焦點(diǎn),所以windows控件focus就跑回了你的按鈕上面了(所以實(shí)際上這里出現(xiàn)了windows focus機(jī)制和html focus機(jī)制的脫節(jié))。怎么證明這一點(diǎn)?我過(guò)去用過(guò)一個(gè)調(diào)試工具可以顯示出每個(gè)html控件實(shí)際的windows控件,但是一時(shí)想不起來(lái)那個(gè)工具叫什么了。不過(guò)此處還會(huì)出現(xiàn)一個(gè)非常orz的癥狀可以證明這一點(diǎn)——若干年前我在進(jìn)行vml編程時(shí)發(fā)現(xiàn)了這個(gè)奇異的bug,這里賣個(gè)小關(guān)子看看大家能否自己發(fā)現(xiàn)它(提示:西方人通常發(fā)現(xiàn)不了這個(gè)bug)。
3. 關(guān)于是用click還是mousedown/mouseup,這其實(shí)就是另外一個(gè)話題了。不過(guò)mousedown中寫(xiě)代碼確實(shí)更加容易觸發(fā)許多非常微妙的IE的bug。
4. 關(guān)于第三個(gè)例子,dexter_yy和Carffuca的說(shuō)法是正確的。這跟排程并沒(méi)有直接的關(guān)系。只不過(guò)setTimeout總是會(huì)在等到當(dāng)前執(zhí)行序列(包括所有同步的事件及相關(guān)函數(shù)調(diào)用)完成之后執(zhí)行,那個(gè)時(shí)候value的值已經(jīng)變化了。
October 31st, 2008 at 18:00
不錯(cuò)的文章,看了自己也寫(xiě)了一點(diǎn)總結(jié)。 見(jiàn)笑
http://hi.baidu.com/webworker/blog/item/3ace11d8ac37543333fa1cb0.html