abbshr.github.io icon indicating copy to clipboard operation
abbshr.github.io copied to clipboard

浅析浏览器中JavaScript中的线程、阻塞与事件

Open abbshr opened this issue 9 years ago • 1 comments

单线程世界里如何处理大量并发任务而不阻塞主线程的执行在做JavaScript开发时显得尤为重要。不过这不是今天的主题。既然浏览器中常常因为滥用JavaScript的事件而导致主线程阻塞,那我们就先来看看客户端JavaScript中的线程。

线程的阻塞

setTimeoutsetInterval是JavaScript中的两个定时器,指定一定时间过后触发某某动作,常用于JavaScript制作的动画效果中。或许你尝试过这样用:

setTimeout(function () {}, 0);

假如你想通过这种方式来实现在0ms之后执行函数,那么你会发现往往事与愿违:函数并没有立即执行。
为什么?这种方式和:

(function () {})();

有什么区别吗?
乍看似乎是一样:直接执行那个函数。不过第一段代码中的函数若想和第二个效果一样,要基于两个前提:时钟周期、代码上下文。
时钟周期:你无法改变,这是有你的机器硬件所决定的,你的函数执行的最小延时时间取决于系统时钟的最小周期,所以即便是你把setTimeOut的延时参数设为0毫秒,触发也不会是即刻的,因为他永远大于系统的时钟周期。一般计算机的最小时钟周期在4ms~15ms左右,所以就算是3ms的延时,函数也不会再3ms之后被触发,而是等到最小时钟周期到了之后。
代码上下文:这个是可以改变的,因为setTimeout的执行环境由你而定,你想让他在那里执行都可以。如果整个代码的末尾执行了setTimeout,handle函数会在最小时钟周期一过便立即执行了,但是如果setTimeOut还有后文(下面还有其他要执行的代码),则首先会将handle函数依次压入事件队列,然后继续向下执行其他代码,等到所有代码都执行完毕,再将事件队列中的事件依次出队列进入事件轮询并执行。因此你会发现:

var arr = [];
setTimeout(function () {
    console.log('fired!');
}, 0);
for (var i = 0; i < 10000000; i++) {
    arr[i] = new String(i);
    console.log(i);
}

这段代码经过了一段时间的控制台疯狂输出从0到10000000的数值后,才打印了“fired!”。其实就算延时稍长一些(假如2ms)也没关系,一样会在超过你设定的延时之后才触发函数的。因为主线程的控制权在这个代码手里,总是先执行完所有的代码,再去理会事件队列,尽管你的延时时间已到,可你也得等在队列里直到所有代码执行完毕,因此特点,setTimeOut常被用作“前端的异步函数”的核心,我写过一个js模块:event.js,用来实现PubSub模式,就是利用了这一特性。

所以,不要幻想0ms会立刻执行代码,也不要奢望指定代码一定会在一段时间后运行!setTimeout在某些情况下的作用仅仅是将handle压入事件队列而已。

线程的挂起

弹出框系列:alert() confirm() prompt() 会启动一个模态对话框,等待你确认。这里我们要讨论的是使用这些弹出框后究竟会对线程的执行和事件的响应有何影响。

可以做一个小测试:

setTimeout("console.log('fired!')", 1000); 
alert('线程挂起!');

这段代码中,注册在1秒之后控制台打印'fired!'事件,然后alert弹出警告框。会发生什么?
alert弹出了一个'线程挂起!'警告框。等待几秒钟,然后点击确认。注意此时控制台什么都没做!随后在点击确认的一秒钟后,控制台打印出了'fired!'。

原因就是alert挂起了主线程。一旦主线程被挂起,整个程序就处于等待状态,不论是事件的触发还是代码的执行都被中断了,是彻彻底底的被打断了,并且就连计时器也被迫暂停计时,直到恢复主线程的使用权为止,余下的代码才继续执行,计时器按照时钟周期重新计时。

这也就解释了为什么即使alert对话框关闭,也要等一秒钟过后控制台才给出响应。

abbshr avatar Oct 27 '14 05:10 abbshr

👍

fakeyanss avatar Sep 27 '18 02:09 fakeyanss