由 John Resig 的 How JavaScript Timers Work 可以知道,现有的 JavaScript 引擎是单线程处理任务的。它把任务放到队列中,不会同步去执行,必须在完成一个任务后才开始另外一个任务。
让我们看看我之前的文章:JavaScript的9个陷阱及评点,在第 9 点 Focus Pocus 中提到的问题。原作者对这个认识有所偏差,其实不只是 IE 的问题,而是现有 JavaScript 引擎对于线程实现的问题(关于线程,我的概念其实不多,如果不对,希望读者多多指教)。我们通过一个例子来说明,请访问 http://realazy.org/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
by 现学现卖
get(’input’).onkeypress = function(){
setTimeout(function() {get(’preview’).innerHTML = get(’input’).value;}, 0)
}
March 30th, 2008
by Lunatic Sun
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
by Lunatic Sun
我刚才说的IE的情况看来是IE的mousedown事件下的一个bug,使用onclick事件就没有这个问题了。
March 31st, 2008
by realazy
The Author
@Lunatic Sun 实际上,从一个不是很专业的角度来说,click = mousedown + mouseup. 虽然我没有深入研究,但可以这么假定:mouswdown 时执行创建 dom 事件,而 mouseup 时执行 focus 和 select 的事件,因而没有问题。这也是在追求速度时,推荐使用 mousedown 替换 click 的原因,只不过 mousedown 不像 click 一样,click 时,用户可以不释放鼠标,从而有反悔的机会。
March 31st, 2008
by Lunatic Sun
@realazy - 从用户体验的角度,我推荐使用click事件的原因有两个:
1 我们应当让用户有反悔的机会;
2 在button的mousedown事件中使用移开焦点的代码input.focus()会使浏览器本身的绘画button被按下和弹起的那种效果消失。
April 1st, 2008
by xiaowei
应该是这样,input.focus();input.select(); 都执行成功。 但由于采用了onmousedown事件,mousedown后随后触发mouseup于是焦点立即移回到button。这样虽然input.select(); 已执行成功但确看不出来
April 4th, 2008
by zamanewby
我前一段时间在写js代码时, 经常出现在firefox ie7 opera等浏览器工作正常的代码, 在ie6下失效的情况。 后来我发现失效部分的代码用一个setTimeout函数延时一下就可以正常工作了, 我通常都是设1ms的延时, 没试过0, 一直以为是ie6的效率问题。 看过这篇文章比较受启发。 回去试一下, 估计正好能解决因为设了1ms延时对后续部分代码影响的问题了:)
April 5th, 2008
by dexter_yy
……问题不是阻断罢,而是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
by realazy
The Author
@dexter_yy 或许我所举的例子不是很好。你所说的 onkeydown/onkeypress的时候,根本就还没有完成输入,我是这样认为的:正是因为 正在输入 这个进程阻断了其他事件,因此才需要
setTimeout来为被隔断的进程重新排程。p.s. 你的 blog 很棒!
April 9th, 2008
by Carffuca
我认为在第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
by lone
我之前对click的理解也是click=down+up, 这些实例把down或者press换成up就行了,理由或许正是LZ讲的 追求速度时推荐使用up而不是down,这样同样允许用户反悔而且会保留按钮的动画效果~
有个实例是 http://www.clickclickclick.com和www.diandiandian.net的点击效果,赫赫
April 11th, 2008
by lone
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.blogbus.com/logs/18841965.html 看您的文章做的些个小实验
April 18th, 2008
by 香酥馍馍片
realazy 看您的第三个例子。
可以去尝试的输入几个字符(中文字也试下)
试的时候:
1.backspace下 ,delete下,情况还是不同的。
2.shift ctrl alt等按键。
可能会有出乎您的预料。
js里内存是怎么回事,
貌似不像上边大家所说的那样。
April 18th, 2008
by lonevan
楼上说的应该是在ie6测试的吧(7,8没试过)
您在firefox下再测试下backspace,delete,shift,ctrl,alt
顺便再加上 up,down,left,right 等等非字母数字键
浏览器解释的差异造成的,不过ie可非w3c~ ^^
April 19th, 2008
by welcome58
楼上诸位都说了很多了,其实input.focus();input.select();都执行了,只是被onmouseup影响了。
第三个例子,我认为这种效果不应该用onkeypress,因为这个时候还没有完成输入动作,改成onkeyup就不存在延迟了,
采用setTimeout,确实可以不改动原有的事件,达到效果,理由博主已经说了,“在实践中,setTimeout 会在其完成当前任何延宕事件的事件处理器的执行,以及完成文档当前状态更新后,告诉浏览器去启用 setTimeout 内注册的函数。”
May 7th, 2008
by 我de艾蜜莉
确实 setTimeout 的问题是存在的,只是 realazy 举的这个例子不太好。
最近就在 ajax 创建 dom 加载内容的时候遇到了这样的问题,这个问题只在 ie 下存在,firefox 没有问题。
May 9th, 2008
by hax
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
by seektan
不错的文章,看了自己也写了一点总结。 见笑
http://hi.baidu.com/webworker/blog/item/3ace11d8ac37543333fa1cb0.html
January 15th, 2009
by GOVO
@zamanewby 我通常也用1ms来解决这些问题的,也没试过0ms,有时,在不需要速度和创建内容结构复杂的东西时,我还用40ms,这个数值好像是从Prototype.js中看到过,才用滴。
而像这样的需要setTimeout才能解决的,我之前只是归类到机器和浏览器性能问题而已,没想楼主想到那么深ORZ
January 15th, 2009
by GOVO
@hax 请你不要卖关子了ORZ