[关闭]
@yacent 2016-10-09T23:31:57.000000Z 字数 2327 阅读 898

JavaScript运行机制(event loop)

性能优化


探讨这个问题的原因,是因为有一次在某博客主页当中看到评论

在一个Ajax请求当中,设置异步回调函数,并且在回调函数的下方,还有别的执行函数,请问是 回调函数先执行,还是下面的其他函数先执行

按照我的理解,代码应该差不多就像下面一样

  1. var xhr = new XMLHttpRequest();
  2. xhr.onreadystatechange = function() {
  3. if (xhr.readystate == 4 && xhr.status == 200) {
  4. document.getElementById("myDiv").innerHTML=xhr.responseText;
  5. }
  6. }
  7. xhr.open('GET', '/index.html', true);
  8. function deepEqual() {
  9. // do something
  10. }
  11. deepEqual();

问题即如上,是onreadystatechange函数先执行,还是deepEqual先执行。
我当时第一感觉是下面的 deepEqual 函数先执行,当然,后来学习了 event loop之后,也证实了我的这种想法是正确的。但是还是要系统的学习一下 event loop机制,才能更有底气


首先,在了解event loop之前,要了解 js引擎的处理机制。

为什么 JavaScript是单线程的?
单线程意味着什么,意味着同一时间只能做一件事。只要等待前面的任务完成,后面的任务才有可能被执行。为什么JS是单线程的呢?我觉得是由于JS的用途所决定的,JS作为浏览器脚本,主要是用来与用户交互以及进行相应的DOM操作,它的功能决定了它只能是单线程的,试想一下,如果JS是多线程的,可以同时开几个线程进行任务的处理,那么在一种情况下,如果一个线程对某DOM结点 A进行属性的修改,而另外一个线程对该DOM结点 A也进行操作,并且是执行remove,那么浏览器应该听哪个线程?
由于JS的功能以及避免过于复杂的逻辑实现,JS一开始就设计为 单线程的了

任务队列
单线程意味着什么?意味着所有任务都要进行排队,前面一个任务做完了,下一个任务才有可能执行,但是如果前面一个任务计算量特别耗时的话,那么后面一个任务不就要等好久?wtf,当然,这里我建议是如果需要进行耗时的计算,用setTimeout进行处理,后面我再做解释。现在这里继续讨论任务队列,而对于异步的任务,也不应要等它数据返回处理完才进行下一任务,而是将其挂起,等他处理完数据,报备说OK了,再通知主线程,将其调入到 执行栈 当中进行任务的执行。

同步任务的执行机制不用过多解释,肯定是占用着主线程的资源,等到任务全部完成才会让出,但是对于异步任务来说,其运行机制应该如下:

1. 所有同步任务都在主线程上执行,形成一个 执行栈 (execution context stack)
2. 主线程外,还存在一个 回调函数队列(callback queue)
3. 一旦 执行栈 当中的所有任务都执行完毕,没有别的任务要执行了,就会去询问 callback queue有没有任务需要执行,如果有,则把队列头的任务放入到执行栈中,开始执行
4. 执行栈 不断地重复 3

上诉机制流程图如下:
任务队列的执行流程图

由于主线程上的 执行栈 在执行完自己的任务之后,总会重复 上面的第三步,询问 callback queue是否有任务需要执行,过程循环不断,所以这种机制又称之为 Event Loop(事件循环)

Event Loop的过程如下图所示
Event Loop的过程
即在 执行栈 当中,会将所有任务都执行,如果在执行过程当中遇到了异步任务,会将异步任务挂起并将其放进到 callback queue当中等待异步请求完成,待 stack当中所有任务都已经完成之后,再去检查callback queue当中是否有任务需要完成,callback queue按照先进先出的顺序进行任务的执行,但是如果某个异步任务是setTimeout而触发的话,除了判断是否已经处理完数据,还需要判断时间是否满足了条件。

所以,回到之前一开始提的问题,onreadystatechange和 deepEqual哪个会先执行,看到这里, 应该会很清晰 deepEqual会先执行,在js脚本解析执行过程当中,首先都会放进 执行栈当中,但是因为onreadystatechange是异步回调函数,会将其放到 callback queue当中,然后继续往下执行,当执行完 deepEqual 任务之后,执行栈当中已无其他任务了(假定),此时会去检查 callback queue当中是否有任务准备好并且要执行了,这时才会执行 onreadystatechange事件


刚才上面还遗留了一个问题,就是说,如果外部调用的js文件运行时间较长的函数,如果一定要用,最好是可以使用 setTimeout函数来进行实现,这里我应该只能简单的说一下好处,就是从浏览器的常驻线程来进行相关的解释

浏览器常驻的五个线程
1. 浏览器GUI渲染线程
2. JavaScript引擎线程
3. 浏览器定时器出发线程(setTimeout | setInterval)
4. 浏览器事件触发线程
5. 浏览器http异步请求线程

所以,如果再需要耗时较长的脚本当中,用setTimeout的话,会调用另外一个线程进行处理。但是,上面的线程当中,如果第二个线程在执行的话,其会挂起其余的一切线程,即阻塞其他线程。

Reference:
https://vimeo.com/96425312 Philip Roberts: Help, I'm stuck in an event-loop
http://www.ruanyifeng.com/blog/2014/10/event-loop.html 阮一峰,javascript运行机制

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注