- 放个面试题,抛个砖:
1 | console.log('start') |
不着急揭晓答案,先分析
首先知晓:
js是单线程语言
也就是说一次就只能做一件事情。多数的网站不需要大量计算,程序花费的时间主要集中在磁盘 I/O 和网络 I/O 上面,虽然SSD读取很快,但和CPU处理指令的速度比起来也不在一个数量级上,而且网络上一个数据包来回的时间更慢(注意过游戏的延迟吗)
so: 一些cpu直接执行的任务就成了优先执行主线任务,然后需要io返回数据的任务就成了等待被执行的任务,所以才会有同步任务(synchronous)和异步任务(asynchronous)之分
同步任务:
在主线程上排队执行的任务,前一个任务执行完毕,才能执行后一个任务;
异步任务:
不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
总之:
只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制
Microtasks Macrotasks
任务队列不止一个,还有 microtasks 和 macrotasks
- microtasks:
1 | process.nextTick |
- macrotasks:
1 | setTimeout |
whatwg规范:https://html.spec.whatwg.org/multipage/webappapis.html#task-queue
一个事件循环(event loop)会有一个或多个任务队列(task queue)
task queue 就是 macrotask queue
每一个 event loop 都有一个 microtask queue
task queue == macrotask queue != microtask queue
一个任务 task 可以放入 macrotask queue 也可以放入 microtask queue 中
理解了这些定义之后,再看执行原理:事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。
还要注意一点:
包裹在一个 script 标签中的js代码也是一个 task 确切说是 macrotask。
所以文首面试题的答案为:
1 | start |
简单来讲,整体的js代码这个macrotask先执行,同步代码执行完后有microtask执行microtask,没有microtask执行下一个macrotask,如此往复循环至结束