参考:
极客时间-消息队列和事件循环
js中的宏任务与微任务
javascript是单线程的原因是什么
JS是单线程语言,在浏览器执行渲染进程时,会遇到很多不同的事件类型,比如操作DOM节点、计算样式布局、执行JS任务、用户输入等,为了保证有序调度,不至于让某个事件堵塞,就需要一个系统算法来统筹调度这些任务,这就是事件循环系统。
每一个JS运行的线程环境中都有一个事件循环的任务队列,队列中的每一个任务被认为是宏任务、每个宏任务中又存在一个微任务的任务队列。在每一次的执行过程中,一般将事件分成同步事件和异步事件,遇到同步事件会先直接放主线程里执行,然后遇到异步事件会再分成宏任务、微任务放进任务队列里,每一次的宏任务执行完成,会再检查微任务队列中的任务,执行完毕后,本次循环结束,进行下一次循环,执行下一个宏任务。
这样既能保证实时性、又能保证一定的效率。
因此事件循环的机制大概步骤如下:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
宏任务包含:
- script(整体代码)
- setTimeout
- setInterval
- I/O
- UI交互事件
- postMessage
- MessageChannel
- setImmediate(Node.js 环境)
微任务包含:
- Promise.then
- Object.observe
- MutationObserver
- process.nextTick(Node.js 环境)
Promise.resolve()
.then(function() {
console.log("promise0");
})
.then(function() {
console.log("promise5");
});
setTimeout(() => {
console.log("timer1");
Promise.resolve().then(function() {
console.log("promise2");
});
Promise.resolve().then(function() {
console.log("promise4");
});
}, 0);
setTimeout(() => {
console.log("timer2");
Promise.resolve().then(function() {
console.log("promise3");
});
}, 0);
Promise.resolve().then(function() {
console.log("promise1");
});
console.log("start");
// 说明,进入某个循环,执行解析,遇到微任务放当前的微任务队列【promise0,promise1】,宏任务放宏任务队列【timer1,timer2】
// OK没有异步任务了,遇到同步代码直接输出,start,然后查询任务队列,先清空当前循环中的微任务,输出promise0,这时又产生了一个微任务加入【promise1,promise5】
// 顺序输出promise1,promise5
// 微任务清空,查看宏任务列表,取出timer1,直接输出timer1,遇到一个微任务【promise2,promise4】执行输出,本次宏任务中没有微任务了,OK进下一个,timer2分析方式同1
// 打印结果: start promise0 promise1 promise5 timer1 promise2 promise4 timer2 promise3
题目分析
各种事件之间是如何相互通知的呢?比如把事件添加到哪个任务队列、任务执行完毕后异步通知
是通过消息队列来通知的
JS为什么是单线程
这主要和js的用途有关,js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。 举个例子:如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom元素,此时浏览器就会一脸茫然,不知所措,如果要对使用的资源上锁的话可能又会加重操作开销。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。