对于浏览器内部,大部分操作都是异步的生成事件并添加到JavaScript引擎线程
的队列中,然后由JavaScript引擎线程
进行调度执行。因此浏览器的很多事件都是和JavaScript
相结合的,但是也有一些内部的限制。
首先我们非常确定JavaScript
是单线程的,对于浏览器来说,一个窗体中只有一个JavaScript引擎线程
。而其他的行为,如:渲染、下载等是由单独的线程进行管理的,且具有不同的优先级。
异步事件
前面提到大多数事件都是异步的,触发的时候就将回调函数添加到事件队列。浏览器提供了一个内部的回路,也就是之前所谈到的Event Loop
,由它来负责检查队列和处理事件、执行函数等。详细可参考我的。而setTimeout
和setInterval
也是将其需要执行的函数添加到事件队列。
事实上,大多数交互和活动都得通过事件循环。
事件重叠
一些情况下,会有多个事件在同一时间附加到事件队列里。
比如,click
事件就会产生两个额外的事件:mousedown
和mouseup
。其中,mouseup
和click
事件会同时被添加到事件队列;而mousedown
事件则很有可能会和另外一个事件重叠:focus
。
setTimeout(func, 0)奇巧淫技
再一次解释关于0ms
的误解:如果当前时钟周期内执行队列空闲,则立即执行该定时器,将回调函数加入到事件队列;然后等待下一个时钟周期,再执行该回调函数。不妨来看看下面的测试。
这段代码在我的浏览器中执行结果如下:
在我本地的Nodejs
环境中执行结果如下:
上面的这个测试只是想说明setTimeout(func, 0)
定时任务的回调函数执行时间是有延迟的,而并不是所谓的立即执行。
因此,我们可以利用setTimeout(func, 0)
来解决事件重叠所产生的负面效果,修正执行顺序。
奇巧淫技之一:模拟浏览器的事件捕获
众所周知,浏览器的DOM事件都是采用冒泡的方式,只有个别浏览器是支持事件捕获的。而在实际的开发过程中可能存在需要事件捕获的需求,要求子元素的事件在父元素触发之后才能触发。为了兼容各个浏览器,我们不能使用事件捕获,而setTimeout(func, 0)
在这个时候就很乐意帮忙了。
12345678910111213141516 | |
奇巧淫技之二:让浏览器更好的工作
大多数情况下,我们可以在浏览器的默认行为之前对事件进行处理,但是有时我们按照常规的思路去做的时候,往往事与愿违。比如下面的例子。
1234567 |
如果没有下一次输入,文本框中的小写字母永远都不会转换为大写。Why? 因为浏览器在keypress
事件处理的时候,还没有将我们输入的值添加到文本框。于是乎换一个事件来handle然后再处理吧,既然键按下的时候还木有值,那就等键弹起来之后再处理。
1234567 |
大概似乎是可行了,可是仔细观察就看出问题了。keyup
事件触发时,文本框已经具备完整的值了,但先是一个小写的值,键完全释放之后转变为大写。这不科学…这太丑陋...
是时候关门放出setTimeout(func, 0)
了。。。
12345678910 |
已经完美了。keypress
事件触发时,将转换大写的操作添加到事件队列,紧接着浏览器添加我们输入的值,然后近乎0延迟的执行我们的转换大写操作函数。
上面两个小案例只是冰山一角,so...合理利用setTimeout(func, 0)
,明天更美好!