简单来说,为了让 JavaScript 运行起来,要完成两部分工作(当然实际比这复杂的多):
在 JavaScript 运行的时候,JavaScript Engine 会创建和维护相应的堆(Heap)和栈(Stack),同时通过 JavaScript Runtime 提供的一系列 API(例如:setTimeout、XMLHttpRequest 等)来完成各种各样的任务。
进程:
- 资源分配的最小单位。
- 指在系统中正在运行的一个应用程序。
- 程序一旦运行就是进程。
线程:
- 程序执行的最小单位。
- 系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。
JavaScript 是一种单线程的编程语言,只有一个调用栈,决定了它在同一时间只能做一件事情。
在 JavaScript 的运行过程中,真正负责执行 JavaScript 代码的始终只有一个线程,通常被称为主线程,各种任务都会用排队的方式来同步执行。
然而 JavaScript 却又是一个非阻塞(Non-blocking)、异步(Asynchronous)、并发式(Concurrent)的编程语言,这就得说说 JavaScript 的事件循环(Event Loop)机制了。
事件循环(Event Loop) 是让 JavaScript 做到既是单线程,又绝对不会阻塞的核心机制,也是 JavaScript 并发模型(Concurrency Model)的基础,是用来协调各种事件、用户交互、脚本执行、UI 渲染、网络请求等的一种机制。简而言之——Event Loop 是 JS 实现异步的一种机制。
Event Loop 包含 2 种:一种存在于 Browsing Context 中,还有一种在 Worker 中。
二者的运行是独立的。也就是说,每一个 JavaScript 运行的“线程环境”都有一个独立的 Event Loop,每一个 Web Worker 也有一个独立的 Event Loop。
Event Loop(事件循环)是通过 任务队列 的机制来进行协调的。
任务队列 的特点:
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
在上述 tick 的基础上需要了解几点:
(macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染——(macro)task->渲染->(macro)task->...
。
宏任务包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel 和 setImmediate(Node.js 环境)。
microtask 可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。
所以它的响应速度相比setTimeout(setTimeout 是 task)会更快,因为无需等渲染。也就是说,在某一个 macrotask 执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。
微任务包含:Promise、async/await、Object.observe、MutationObserver 和 process.nextTick(Node.js 环境)。
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
setTimeout(() => {
console.log(6);
})
console.log(7);
// 输出结果:1 4 7 5 2 3 6
解析:
首先需要知道的知识点包括:
执行步骤:
function fn2 () {
console.log(1);
return new Promise(resolve => {
resolve(2);
console.log(3);
})
}
console.log(4);
fn2().then(res => console.log(res));
console.log(5);
// 输出结果:4 1 3 5 2
解析:
async function fn1 () {
console.log(1);
await new Promise(resolve => {
resolve(2);
console.log(3);
})
console.log(4);
return 5;
}
console.log(6);
fn1().then(res => console.log(res));// 不会打印 2,而是会打印 5
console.log(7);
// 输出结果:6 1 3 7 4 5
解析:
首先需要知道的知识点包括:
执行过程如下:
【参考文章】
深入理解 JavaScript Event Loop-阿里 CCO 体验技术专刊首发
js中的宏任务与微任务
JavaScript 运行机制详解:再谈Event Loop