js中setTimeout()时间参数设置为0的探讨

时间:2023-03-09 19:17:03
js中setTimeout()时间参数设置为0的探讨

起因源于一道前端笔试题:

var fuc = [1,2,3];
for(var i in fuc){
setTimeout(function(){console.log(fuc[i])},0);
console.log(fuc[i]);
}

问:控制台会如何打印?

chrome打印结果如下:

js中setTimeout()时间参数设置为0的探讨

虽然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函数原意是监听到用户输入字符串就将其完整的显示出来,但是奇怪的是最后一个字符串总是没能显示出来:

aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOIAAAApCAIAAACnXWL3AAACkUlEQVR4nO2bwY6CMBCGea4+EM/De2z2tEkvxouvwcGNJpu4MVkPJh7dA1Da6UyBouLo/4XL0hYG+9EpZSmuADw9xdIBADAMNAUKgKZAAdAUKACaAgVAU6AAaAoUAE2BAnpNt9+71Xrz8fmFTde2Wm+237sFHXoAvaa7/e/heFowFJDH4Xiqtz9LR3FfAk0XjAPMAZoCBUBToIC0pnVlTFXHOwtHaaWisMRhS1LY7CikQ84Fmr4CkqZOHqJpIG5jpdPKlq6orkzUtD2sMSbSlKl5K6DpK8BqastGnGGDbCkMf6yntixMZatn0bS7TC+Qgt6cfn6IogxCD5JM8KtItzwYTTLpDxtUE+dSBa0VtGQRTTtzvBPXlemiqivTlrA7+7+p0MwvYctbT2PekXmaSrmd2e968Sk07c4snJh1K/CwaesfgdVUcBdMZJamUcp36Y12jTcUcZre6/npmqcpHThpbScf1ZReRl2ZZi5+n2t7H/I1Jc9Pccu+MOh2cZ7QHfK2Q+t0TaMAOwHbyt5Qyx+hzyX+D4jsP4NcTW05IFTfV6TbE5qmT5nHVE3lAFrP6KMSd692SgZqwtN8sjSVpqRRY6FbE0lQXDrIZZKmg+kjrs9W74wMmshTYTDEdE2TyZ5UG5VTR7XKZtqCVLyE5iLlJqzhfNWf47RXGKwZYDDNZqqm8mjn9VPSZdJf3juBsXfAJNILUt6cM3ob5lbP5NHfF91vzz5WYijNR1re51M0n8CD2VcqozdnpMMKu6x+M/AW6hXAv54ABUBToABoChTwXpriIxONvNdHJufzZX/4w6ZxO58vCzr0APABNFAANAUKgKZAAdAUKACaAgVAU6AAaAoUAE2BAqApUAA0BQr4B3wuazm7Ue5EAAAAAElFTkSuQmCC" alt="" />,

但是只要改下onkeypress函数就好:

  $('input').onkeypress = function(){
setTimeout(function(){$('preview').innerHTML = $('input').value;},0);
}

将onkeypress里面的事件延迟到浏览器更新相关DOM元素的状态之后执行,这是就能显示出所有字符串了,如下:

aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAASMAAAAmCAIAAAAA+Gp9AAADfUlEQVR4nO2cTW7kIBCFORcH4jzcYzSrkdhE2eQaXnTUkSL1KNJk0VKWmYUNpiiwMd0G2nmfLE1sqHLZ1OPPoxbfAID9Ea0DAOBHAKUBUAMoDYAaQGkA1ABKA6AGUBoANYDSAKgBlAZADYjSTq/np+eXX7//4His4+n55fR6bpVDIAeitPPb38vHZ6tQQDGXj8/h9N46CrBEqLRWcYAbgdI6B0o7CFBa50BpB2FZaYOWUg/8onAokyqiJb04tBiVKCt1uIxRQnC3OUBpByGltDETBcsPkohjArrMM8oVDVoGpl049GyllEHBLQ5X4NFnA6UdhKjSjBrTYr0nNiqRdzS3+nJolJDaaKK0O0S4wG5Ks2H7sYX9hT8gBzFUMPcLbzCPE3jomsXZ43raDTocGRYLenA4tU7iRhschpIziqbIVNFPIFdhus1Ytni/pNKsX3pHG8+g5VQSvVjN3HsVfuVt5kl+jtJSfXXqenuHg5ajOkqV5jmkdadsIsOkPWPhGyWElJEVIWPTmBY8CHs89/RVzcdqvPKqOVGcX3vQUihN1/DzTKVsKb03NymNTaRc/5141NYOvaYrVBpx6FcetKRrPz/ZokrL644LlRYdFljtCuZOnRuU5sw9aRslpFTziMp85r/SNpQrbXFvILG119YhSZ8SpcUc+pngd9z8ZkxpWT1vkdLYw9nFVl6u38/c626ylcbf6vekrUEreyLpH2lv3VCqtNUeJDrfa+kwSJ/tSotEODvxVn+uNw5mlmz2mJUXBUpLOw/nhHubk68jbHKSc/dp9HL/TC95qnF4pWVtpcW67YYOI40em5hujNAKyjUzuxC33k9pq4NyEEY185wxLb5BqYydLdozb1/mwErL/KKUPwS1cbhlTFtwOCafmT+5jUuLoNlrKS3m1igXOFt97W+erJxrPmgp5bwRY5SQUnpnpORISksvMLxd2qUvxg0dht4zlbbs0O6E+Dtt9ELMxx2U5jaL6LcDgp3QRsbvSubkFcwXNpgHDUVnsDY6u/f4aEpjD23fcnwKRpKMN0pPDr0H5w1Y5pDt1cQ3b+wN5rS6x5gGHgb8D+POgdIOApTWOVDaQYDSOgdKOwhQWufg1w2OAH7doH+I0q7Xr7fLPxyPeFyvX61yCOSAX6EDoAZQGgA1gNIAqAGUBkANoDQAagClAVADKA2AGvwHnAEVjvJJYNwAAAAASUVORK5CYII=" alt="" />

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后,控制台打印结果如下:

aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKQAAAA+CAIAAAAqKiczAAACnUlEQVR4nO2c3W2DMBSFM4SlSGlEEVUklKcMwAg8MAUSQ2QBVmCGDMQ2fbjS1Y1t0vzgxu050vdADJiKz7lG+DSbj88vAsLGbXcEBMoGgrKBoGwgKBsIygZifdmX8+F03K/S1TzVb79B/wnKBiKh7KEr56lW8fNUC21TuO3udNxbl5fzwbYrstc2ymFDVw5dqafrx3mq5bqyoQdID2uNwj9KKtlDV17OB9uo26rQCh77Svfa8eE1qrO2KeQUOWboSulqnmppPx339qKU7RLJnqc6vNEWuekqbOwrr0Udh41jX7VNIeNDjMoB0oOtFpwFPJLIlkKqLd6XzCKH6d6HZMuF2qaQKkLZP5KqjLdN4d13KbMeY1+JPPloJ3KpEO66AtttnZXHvtLhsiSbZdwlfUAT3+FjV1jh7en6eGWHi2zbKUAupLMAZd/Dm1+qeA/VJClvky3fYDsZk9TwdSkQlA0EZQNB2UBQNhCUDQRlA0HZQFA2EJQNRNaxJLIulA1Edhm0aIhM1rzDFVKvT3e9lqqnRxsd3rpnjhm0MI9mV6w17LDUZyhvqdhQ9gqyn86guevEgY4Gm2a53adUBW+NPNoISF4ZNBfI1m92KHupT7ewWM4V9LwyaM7Itj2Esm/0KURHA3i+OLsMWvgfAm5BdrTPsLAvNVL2bxNm0Jj/TUd2GTTKTgdflwJB2UBQNhCUDQRlA0HZQFA2EJQNBGUDQdlAoMSSZJX9xioZAhCy5U/SKAQsOWbQtNH+NNbrv25G2Tlm0MIjo8E0R9kPknUGzcp+3RllZ51BW5L93DMBZeeYQdOQYSjb65Zl/CGyy6DZBzErOwymubtly8wS7QGK7DJoUZANrUh2GbQolL0KfF0KBGUDQdlAUDYQlA3ENzGEhMLtoLveAAAAAElFTkSuQmCC" alt="" />

也就是先执行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");
}

结果如下:

aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAARIAAABjCAIAAADYXBikAAAD7UlEQVR4nO3d0WkqTRiA4VOEEJAQRBAkVykgJeTCKgSLsAFbsAYr+Suwm/9iYBh2RpNPcGeE5/DA8ayu4SR5mV11Zv8t3paLt+V/1Z+0Haj9S3/JBv5ONhAmGwiTDYR1y+Zy3FzP2+t5e9qve/3nr+dt9x8Ar6hPNj/fHx1ryWTDY/pkc9itDrtVueXr8z0NPtfz9uf7I28/7deTQan8Xc+3y93LZ66fs3ykbHjM3Nkcdqvyt/Z63n59vi/elvlG+rVOt0/7dT0oNbO5HDdpl9LluKkfmZ98YbThUUOMNpNjttN+nQaH8lc8a2aTaiyfczKqpKeafCHZ8JjRs6n3bWaTnzYfzn19vpejTfMLyYbHDJFNeWA2OUibnAItiiEonfZM7i1rmZwm5SdPt9NLed1/ALyiIbJZvC1/vj8mZztJ3phHiXx2lG5MHlbuXh6n5Zby7ukrdv8B8Iq83QlhsoEw2UCYbCBMNhAmGwiTDYTJBsJkA2GygTDZQJhsIKxzNj5MySuSDYR1yKY5m79eS2AyuSD/M08ZmEznhNl0yKY5m7+eppZnYqbHHHarPOXzzvxNmMHc2TSnJTcnRX99vp/269RGekBerGOyO8xs6Gwux00aZA67VV6bRjZ0N3c2zdn8t9YSyGcvp/26XCEgP5ts6KLDuU1zNn9zLYHLcZNGIdkwFG93QphsIEw2ECYbCJMNhMkGwmQDYbKBMNlAmGwgTDYQJhsI65ZN+vhzeb0neBV9splMsIHXMspFCOu1BJJ0gc5yUGpOHCh3t8AAzzZ3NnmyzWR2za1L3taDUjObPPcTZjDEaHPnAut1DM1sUo3GGeYxejb1vndmd6Z4nDXxbENkc2stgdN+XQ8g5b11V1aBYgZDZLO4sZbA4m2ZN+YxpFxbsFxmrbk7PIO3OyFMNhAmGwiTDYTJBsJkA2GygTDZQJhsIEw2ECYbCJMNhMkGwmQDYX0usF7fzhMBTNJkfANlUy6yYc4MIxsom7zxctyUi9fAaAbNxmjDyPpkUy8GMLnSevfvC9zRIZtfFwPo/k2B+0Z5AVotvBDZQNgo2cALkQ2EyQbCZANhsoEw2UCYbCBMNhAmGwgbPZt0lSjzCBjKKNlcjpvm5zj/mI25BsxplGyShz+ZJhvm1CebNNOmvjztJJtbQ1C9e84mTUaQEE/VIZvTfn3rYs6/Xhr61u4pm8Nu5Xq3zKDn7M7mXb9ubO6exiXNMI/Oawn85a46m/oxl+OmnCsKT9XnIO3WSmh/PEird08HadYhYB59XhLIywaUa6Nl+VirubG5e35JIJXjfR6eaqwXoOElyAbCZANhsoEw2UCYbCDsf66oOMlaLPd5AAAAAElFTkSuQmCC" alt="" />

可见先执行mousedown事件,然后生成的input获得focus,接着按钮获得focus,接着执行mouseup事件,再执行click事件,和key的3个事件的执行顺序有些不同。

看到这里恍然大悟,这个生成的input确实是获得了focus,但是随之失去focus,因为按钮的mousedown事件紧跟着按钮的focus事件,被按钮夺取了focus。

将 input.focus(); 改为 setTimeout(function(){input.focus();},0);得到结果为:

aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAREAAABmCAIAAABjpjIUAAADr0lEQVR4nO3d0W3aUBSA4QyBFClFKaJCQjwxQEbIA1MgMQQLsAIzMEknYJs+HOnKtU3DaevcS/tV34NjcCIl/WVDcq6fvnz9Btzvafb8Unwf/Os+CsyeXzQDOZqBHM1AjmYgRzOQ00Qz1/O6+jcC7qQZyKnWzHYzv57XxXDn+9vr7PnlsFscdotyVPnwel4fdot4ZvcJMLVqzVzP6+1mXrZ7OyOe7Wb+/vZ62i/Lcw67RbR0Pa9j/3YzvxxX1b+P/D/qNFNKCNFDb+dpv3x/e91u5qf9MsKIJ0RU3cs5l3Z8pgdo5nJcxenlsFtcjivNUFedZuLSK7Yvx1Vsl+ux3nZ5xXLaL8tlmGaopdrrmfIK/v3ttfynj+1QXu1cjqs4/2iGFjTxXjM8EM1AjmYgRzOQoxnI0QzkaAZyNAM5moEczUCOZiBHM5BTuZn4o+YyQAbtq9lMb2AGHkLNZnqz/rOx9QDCab/snY5GZwG6h1skgInUaaYMz/SmZUZnzk775fB0NNpMmeKE6TR0nhmdbZ79vNpGMdpMpOgMw6Qeo5nhsb+Y04xyvFJiIg01c2s9gNN+OTx1dB8dRmUBJ6bTUDOzG+sBzJ5fys5y9uguCNhdHm30cPiL/E4TcjQDOZqBHM1AjmYgRzOQoxnI0QzkaAZyNAM5moEczUCO9QDc8okc6wFohpy2ZgEmWg9g+Dm7z9QMKf/+egDd4bPupE15pmZIaeg8M8V6AL3zSXyq0Tutw50eo5nhsXeuBzA65KwZ/kRDzUy0HkDvpVH55LEdb9xV/zHwQBpqZjbNegDdy7MSUjk8vmL1HwMPxO80IUczkKMZyNEM5GgGcjQDOZqBHM1AjmYgRzOQoxnI0QzkaAZyNAM51ZoZHRorf9vv9ss0q7lmuqtkuCcmDWqumbLzclz15iuhBU034zxDg2o2MxzoLxtGjmlWtWY+HOiv/q2BUW291ywV2qcZyGmrGWifZiBHM5CjGcjRDORoBnI0AzmagRzNQM5jNBP3kDEaQAvaaibuOjZ637J7mjE+wCdoq5nw2391phk+Qc1mYnKmO88ces3cOvkMDy/NxHyBfphCtWZO+2UvleLDGzXfOjyaOewWw3s1w99Sf05z9KEPd44eHmckwTCpJtYDuOehYTPD51yOq+7UJ0yh5rXZrRXM7rw2Gx4e12bWEmBSNd8DKKP/3TXNinKJNbpz9PDyHkBk4/c5TKHF95qhZZqBHM1AjmYgRzOQoxnIeZr6C8A/5gd2dyEolPl4aAAAAABJRU5ErkJggg==" alt="" />

也就是通过延迟执行input获取focus事件,最终就是生成的input获取了focus。

这样的话,通过绑定mouseup,click事件同样能使生成的input夺取按钮的focus事件,事实证明确实如此,就不贴代码上来了。

如果绑定focus事件会怎样呢?这一次,chrome和ie站在了统一战线,都是生成一个获取了focus的input,但有趣的是firefox居然生成了2个。

不过ie反应也不算正常,绑定mousedown时不使用setTimeout的输出是这样的orz:

js中setTimeout()时间参数设置为0的探讨

也就是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/