Open JTangming opened 5 years ago
结合以下描述,看图理解。
JavaScript 是单线程、非阻塞的脚本语言,单线程指同一个时间只能做一件事。
JavaScript 的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
而非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。
JavaScript 的任务分两种:一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有某个异步任务可以执行了,该任务才会进入主线程执行。
JavaScript 的运行机制如下:
主线程不断重复上面的第三步。所以整个的这种运行机制又称为 Event Loop(事件循环)。只要主线程空了,就会去读取"事件队列",这就是 JavaScript 的运行机制。
事件队列包括:Task Queue 和 MicroTask Queue
javascript 代码运行分两个阶段:
异步任务分为宏任务和微任务,宏任务队列可能有多个,而微任务队列只有一个:
以上仅仅讲了大致的执行流程,下面结合微任务和宏任务细说一下执行 JavaScript 代码的具体流程:
tips:执行宏任务的过程中,遇到微任务,依次加入微任务队列,会在下一次微任务中被执行。如果在执行 microtask 的过程中,又产生了 microtask,那么会加入到队列的末尾,会在当前这个周期被调用执行。
浏览器为了能够使得 JS 内部 Macrotask 与 DOM 任务能够有序的执行,会在一个 Macrotask 执行结束后,在下一个 Macrotask 执行开始前切换至 UI 线程,对页面进行重新渲染,流程如下:
(macro)task->渲染->(macro)task->...
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
事件循环的模型:
┌───────────────────────┐
┌─>│ 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:
参考文章