由 John Resig 的 How JavaScript Timers Work 可以知道,现有的 JavaScript 引擎是单线程处理任务的。它把任务放到队列中,不会同步去执行,必须在完成一个任务后才开始另外一个任务。
让我们看看我之前的文章:JavaScript的9个陷阱及评点,在第 9 点 Focus Pocus 中提到的问题。原作者对这个认识有所偏差,其实不只是 IE 的问题,而是现有 JavaScript 引擎对于线程实现的问题(关于线程,我的概念其实不多,如果不对,希望读者多多指教)。我们通过一个例子来说明,请访问 http:///lab/settimeout.html. 我们来看 1 和 2。如果你能看看源代码,会发现我们的任务很简单,就是给文档增加一个 input
文本框,并聚焦和选中。请现在分别点击一下,可以看到,1 并没有能够聚焦和选中,而 2 可以。它们之间的区别在于,在执行
input.focus();
input.select();
时, 2 多了一个延迟时间为 0 的 setTimeout
的外围函数,即:
setTimeout(function(){
input.focus();
input.select();
}, 0);
按照 JavaScript: The Definitive Guide 5th 的 14.1 所说:
在实践中,
setTimeout
会在其完成当前任何延宕事件的事件处理器的执行,以及完成文档当前状态更新后,告诉浏览器去启用setTimeout
内注册的函数。
其实,这是一个把需要执行的任务从队列中跳脱的技巧。回到前面的例子,JavaScript 引擎在执行 onkeypress
时,由于没有多线程的同步执行,不可能同时去处理刚创建元素的 focus
和 select
事件,由于这两个事件都不在队列中,在完成 onkeypress
后,JavaScript 引擎已经丢弃了这两个事件,正如你看到的例子 1 的情况。而在例子 2 中,由于setTimeout
可以把任务从某个队列中跳脱成为新队列,因而能够得到期望的结果。
这才是延迟事件为 0 的setTimeout
的真正目的。在此,你可以看看例子 3,它的任务是实时更新输入的文本,现在请试试,你会发现预览区域总是落后一拍,比如你输 a, 预览区并没有出现 a, 在紧接输入 b 时, a 才不慌不忙地出现。其实我们是有办法让预览区跟输入框同步地,在此我没有给出答案,因为上面所说的,就是解决思路,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 引擎并不会丢弃事件,在你的例子中
input.focus();
input.select();
已经被执行。并且input不能获取焦点的解决方法不一定是使用setTimeout,在Firefox和Safari中,只要使用return false取消默认行为就能够达到目的。另外Opera不需要任何技巧直接能够正确执行以上两行代码,我想可能是因为Opera没有在button的mousedown事件上设置默认行为。
不过return false没有解决IE的这个问题,IE确实是执行focus和select方法,input也确实得到了焦点,但是似乎select的默认行为没有被执行,所以IE中看不到input中的文本被选中。
March 30th, 2008 at 12:49
我刚才说的IE的情况看来是IE的mousedown事件下的一个bug,使用onclick事件就没有这个问题了。
March 31st, 2008 at 10:38
@Lunatic Sun 实际上,从一个不是很专业的角度来说,click = mousedown + mouseup. 虽然我没有深入研究,但可以这么假定:mouswdown 时执行创建 dom 事件,而 mouseup 时执行 focus 和 select 的事件,因而没有问题。这也是在追求速度时,推荐使用 mousedown 替换 click 的原因,只不过 mousedown 不像 click 一样,click 时,用户可以不释放鼠标,从而有反悔的机会。
March 31st, 2008 at 10:55
@realazy - 从用户体验的角度,我推荐使用click事件的原因有两个:
1 我们应当让用户有反悔的机会;
2 在button的mousedown事件中使用移开焦点的代码input.focus()会使浏览器本身的绘画button被按下和弹起的那种效果消失。
April 1st, 2008 at 14:30
应该是这样,input.focus();input.select(); 都执行成功。 但由于采用了onmousedown事件,mousedown后随后触发mouseup于是焦点立即移回到button。这样虽然input.select(); 已执行成功但确看不出来
April 4th, 2008 at 21:15
我前一段时间在写js代码时, 经常出现在firefox ie7 opera等浏览器工作正常的代码, 在ie6下失效的情况。 后来我发现失效部分的代码用一个setTimeout函数延时一下就可以正常工作了, 我通常都是设1ms的延时, 没试过0, 一直以为是ie6的效率问题。 看过这篇文章比较受启发。 回去试一下, 估计正好能解决因为设了1ms延时对后续部分代码影响的问题了:)
April 5th, 2008 at 05:44
……问题不是阻断罢,而是onkeydown/onkeypress的时候,根本就还没有完成输入罢, value本来就没改变,同样, onmousedown的时候点击事件还未完成,select和focus实际上执行过了,只不过又被点击事件取消了而已……
你可以把那个测试页面的代码改成这样试试:
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 或许我所举的例子不是很好。你所说的 onkeydown/onkeypress的时候,根本就还没有完成输入,我是这样认为的:正是因为 正在输入 这个进程阻断了其他事件,因此才需要
setTimeout
来为被隔断的进程重新排程。p.s. 你的 blog 很棒!
April 9th, 2008 at 18:09
我认为在第3个例子中,我们定义了对onkeypress事件的处理函数(即在span中显示input内的值),而浏览器自身也有一个对onkeypress事件的处理函数(即在input框中显示你输入的那个值)。我认为浏览器把这两个函数放在了一个对onkeypress事件监听的队列里,并且用户定义的函数先运行了,浏览器自己的函数后运行。可以简单的修改一下第3个例子就能看出这个效果。
get(’input’).onkeypress = function(){
alert(this.value);
}
注意当alert出现的时候input框中并没有值,当点击alert的确定后,input框中出现了输入的值。
在这里使用setTimeout其实就是推迟了用户定义的那个函数的运行时间,浏览器会在处理好所有onkeypress事件监听函数后运行setTimeout中的内容。
April 11th, 2008 at 10:49
我之前对click的理解也是click=down+up, 这些实例把down或者press换成up就行了,理由或许正是LZ讲的 追求速度时推荐使用up而不是down,这样同样允许用户反悔而且会保留按钮的动画效果~
有个实例是 http://www.和www.diandiandian.net的点击效果,赫赫
April 11th, 2008 at 21:19
1) Javascript不会丢弃事件, 只要在select()语句后再加上一句 alert(’after select’)方法,执行就可以看到此时,input已经被选中;
2) JS引擎首先执行用户自定义事件处理方法,然后才执行默认行为;
3) JS引擎碰到setTimeout方法会将其放入队列等待, 并”跳过”其程序块而继续执行其所在方法的后续代码,执行完成之后才从队列中调用setTimeout~
因此,以下代码同样可以解决问题:
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> 执行onmousedown程序体;
2> 跳过setTimeout方法,没有其他代码;
3> 执行setTimeout方法,没有问题;
ps: lz有必要删除留言么? …………….
http://lonevan./logs/18841965.html 看您的文章做的些个小实验
April 18th, 2008 at 11:10
realazy 看您的第三个例子。
可以去尝试的输入几个字符(中文字也试下)
试的时候:
1.backspace下 ,delete下,情况还是不同的。
2.shift ctrl alt等按键。
可能会有出乎您的预料。
js里内存是怎么回事,
貌似不像上边大家所说的那样。
April 18th, 2008 at 16:35
楼上说的应该是在ie6测试的吧(7,8没试过)
您在firefox下再测试下backspace,delete,shift,ctrl,alt
顺便再加上 up,down,left,right 等等非字母数字键
浏览器解释的差异造成的,不过ie可非w3c~ ^^
April 19th, 2008 at 23:40
楼上诸位都说了很多了,其实input.focus();input.select();都执行了,只是被onmouseup影响了。
第三个例子,我认为这种效果不应该用onkeypress,因为这个时候还没有完成输入动作,改成onkeyup就不存在延迟了,
采用setTimeout,确实可以不改动原有的事件,达到效果,理由博主已经说了,“在实践中,setTimeout 会在其完成当前任何延宕事件的事件处理器的执行,以及完成文档当前状态更新后,告诉浏览器去启用 setTimeout 内注册的函数。”
May 7th, 2008 at 23:40
确实 setTimeout 的问题是存在的,只是 realazy 举的这个例子不太好。
最近就在 ajax 创建 dom 加载内容的时候遇到了这样的问题,这个问题只在 ie 下存在,firefox 没有问题。
May 9th, 2008 at 19:03
1. 并不是所有浏览器都支持setTimeout延时为0。而且它们的定时排程也各有差异。
2. 你的例子其实有问题,所以你后面的论述统统都白搭了,因为你正好碰到了IE的bug(Lunatic Sun的判断是对的,只是这个bug的本质不是那么容易认识到),所以什么问题也证明不了。IE的HTML focus等实际是被转换为windows控件的focus,所以这里存在两种不同层次的focus机制。理想上,HTML的focus应该被同步转换为windows控件的focus,然而IE可怜的代码导致这种转换存在某些不同步的bug。你不幸就遇到了。实际上,即使没有使用setTimeout,调用focus()之后,HTML focus确实已经到了新生成的input中,这一点你可以通过document.activeElement来验证。然而,由于mousedown事件默认会获得控件焦点,所以windows控件focus就跑回了你的按钮上面了(所以实际上这里出现了windows focus机制和html focus机制的脱节)。怎么证明这一点?我过去用过一个调试工具可以显示出每个html控件实际的windows控件,但是一时想不起来那个工具叫什么了。不过此处还会出现一个非常orz的症状可以证明这一点——若干年前我在进行vml编程时发现了这个奇异的bug,这里卖个小关子看看大家能否自己发现它(提示:西方人通常发现不了这个bug)。
3. 关于是用click还是mousedown/mouseup,这其实就是另外一个话题了。不过mousedown中写代码确实更加容易触发许多非常微妙的IE的bug。
4. 关于第三个例子,dexter_yy和Carffuca的说法是正确的。这跟排程并没有直接的关系。只不过setTimeout总是会在等到当前执行序列(包括所有同步的事件及相关函数调用)完成之后执行,那个时候value的值已经变化了。
October 31st, 2008 at 18:00
不错的文章,看了自己也写了一点总结。 见笑
http://hi.baidu.com/webworker/blog/item/3ace11d8ac37543333fa1cb0.html