众所周知
JS
是门非阻塞单线程语言,因为在最初JS
就是为了和浏览器交互而诞生的。如果JS
是门多线程的语言话,我们在多个线程中处理 DOM 就可能会发生问题(一个线程中新加节点,另一个线程中删除节点),当然可以引入读写锁解决这个问题。
JS
在执行的过程中会产生执行环境,这些执行环境会被顺序的加入到执行栈中。如果遇到异步的代码,会被挂起并加入到Task
(有多种task
) 队列中。一旦执行栈为空,Event Loop
就会从Task
队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说JS
中的异步还是同步行为
console.log('script start');setTimeout(function() {console.log('setTimeout');
}, 0);console.log('script end');
- 以上代码虽然
setTimeout
延时为0
,其实还是异步。这是因为HTML5
标准规定这个函数第二个参数不得小于4
毫秒,不足会自动增加。所以setTimeou
t还是会在script end
之后打印。 - 不同的任务源会被分配到不同的
Task
队列中,任务源可以分为 微任务(microtask
) 和 宏任务(macrotask
)。在ES6
规范中,microtask
称为jobs
,macrotask
称为task
。
console.log('script start');setTimeout(function() {console.log('setTimeout');
}, 0);new Promise((resolve) => {console.log('Promise')resolve()
}).then(function() {console.log('promise1');
}).then(function() {console.log('promise2');
});console.log('script end');
// script start => Promise => script end => promise1 => promise2 => setTimeout
- 以上代码虽然
setTimeout
写在Promise
之前,但是因为Promise
属于微任务而setTimeout
属于宏任务,所以会有以上的打印。 - 微任务包括
process.nextTick
,promise
,Object.observe
,MutationObserver
- 宏任务包括
script , setTimeout ,setInterval,setImmediate ,I/O ,UI renderin
很多人有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了
script
,浏览器会先执行一个宏任务,接下来有异步代码的话就先执行微任务
所以正确的一次 Event loop
顺序是这样的
- 执行同步代码,这属于宏任务
- 执行栈为空,查询是否有微任务需要执行
- 执行所有微任务
- 必要的话渲染
UI
- 然后开始下一轮
Event loop
,执行宏任务中的异步代码
通过上述的 `Event loop` 顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作 `DOM` 的话, 为了更快的 界面响应,我们可以把操作 `DOM` 放入微任务中