JavaScript运行机制 & Event Loop
JTangming opened this issue · 5 comments
JavaScript 是单线程、非阻塞的脚本语言,单线程指同一个时间只能做一件事。
JavaScript 的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
而非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。
JavaScript 的任务分两种:一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有某个异步任务可以执行了,该任务才会进入主线程执行。
理解 Event Loop
JavaScript 的运行机制如下:
- 所有同步任务都在主线程上从头开始执行,将当前的执行环境压入执行栈(execution context stack)等待执行,如果是一个方法,则加入执行栈的是当前方法的执行环境(私有作用域、上层作用域的指向、参数、变量和this等)
- 在主线程之外,存在一个"事件队列",当某个异步任务达到可执行的状态时,就推入到"事件队列"之中
- 当"执行栈"中的所有同步任务执行完毕,系统就会读取"事件队列"。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
主线程不断重复上面的第三步。所以整个的这种运行机制又称为 Event Loop(事件循环)。只要主线程空了,就会去读取"事件队列",这就是 JavaScript 的运行机制。
事件队列包括:Task Queue 和 MicroTask Queue
javascript 代码运行分两个阶段:
- 预解析,把所有的函数定义提前,所有的变量声明提前,变量的赋值不提前
- 执行,从上到下执行(按照js运行机制)
微任务(Microtask)与宏任务(Macrotask)
异步任务分为宏任务和微任务,宏任务队列可能有多个,而微任务队列只有一个:
- 宏任务包括:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering
- 微任务包括: Promise(), process.nextTick, Object.observe(已废弃), MutationObserver(html5新特性)
以上仅仅讲了大致的执行流程,下面结合微任务和宏任务细说一下执行 JavaScript 代码的具体流程:
- 执行全局 Script 同步代码,当所有同步任务执行完毕之后,清空执行栈,接下来先执行微任务队列里的任务
- 从微任务队列(microtask queue)中取出位于队首的任务,放入执行栈中执行,执行完后 microtask queue 长度减 1,直到所有微任务执行完毕
- 微任务队列里的任务全部执行完毕后,会读取宏任务队列中拍最前的任务
- 执行栈空后,再次读取微任务队列里的任务,依次类推不断的重复执行。
tips:执行宏任务的过程中,遇到微任务,依次加入微任务队列,会在下一次微任务中被执行。如果在执行 microtask 的过程中,又产生了 microtask,那么会加入到队列的末尾,会在当前这个周期被调用执行。
浏览器为了能够使得 JS 内部 Macrotask 与 DOM 任务能够有序的执行,会在一个 Macrotask 执行结束后,在下一个 Macrotask 执行开始前切换至 UI 线程,对页面进行重新渲染,流程如下:
(macro)task->渲染->(macro)task->...
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,执行完当前所有微任务,开始检查渲染,然后 GUI 线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
node环境下的事件循环机制
事件循环的模型:
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<──connections─── │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
node中事件循环的实现是依靠的libuv引擎。
chrome v8 引擎将 js 代码解释后去调用对应的 node api,而这些 api 最后由libuv引擎驱动,执行对应的任务,并把不同的事件放在不同的队列中等待主线程执行。
因此实际上 node 中的事件循环存在于 libuv 引擎中。
Reference:

