这是我参与更文挑战的第3天,活动详情查看: 更文挑战
js单线程
js是一门浏览器的脚本语言,主要用于实现用户与浏览器的交互,以及操作dom,是一门单线程语言。由于js是单线程,所以所有的任务只能按顺序执行,执行完一个再执行下一个。可能有的人会问js为什么只能是单线程而不能是多线程呢?那我们现在就来做个假设,假设javascript是多线程语言,多线程是同时可以执行多个线程的操作,一个线程在某个DOM节点上执行添加的操作,另一个线程执行删除该节点的操作,js会再添加的同时进行删除操作,那么问题就来了,js是该执行添加还是该执行删除操作呢?因此,javascript的功能只能决定其是单线程语言。
同步任务和异步任务
js的任务分为同步任务和异步任务,同步任务会直接进入到主线程立即执行,而异步任务则会先进入到事件表(Event Table),等异步任务执行完成之后就会在Event Queue(事件队列)中注册回调函数,只有主线程任务全部完成后,才会执行Event Queue中的回调,js解析器会不断重复检查主线程的执行栈是否为空,然后再重复循环执行,这就是Event Loop(事件循环),如下图所示
宏任务和微任务
js任务还可分为宏任务和微任务
宏任务(macro-task)包含 script、定时器相关的异步代码(setTimeOut、setInterval)、I/O、UI Rendering
微任务(micro-task)包含 Promise.then、process.nextTick
注意:
- 同步的代码会按照执行顺序依次执行,遇到异步代码的时候,宏任务的放到宏队列,微任务放到微队列,其中promise需要resolve或者reject才会执行then或者catch里面的内容。
- 任务的执行顺序是宏任务-微任务-宏任务……,因为整个script其实就是一个宏任务,所以当js代码里的宏任务和微任务放入队列之后,就会先执行微任务再执行宏任务;前提是之前的任务执行完毕,如果存在嵌套关系,则会先执行完该任务再执行下一个任务,具体流程可参考下图
- process.nextTick只能在node环境下运行
总结一下js的执行机制
先同步任务后异步任务
遇到new Promise先执行(Promise.then属于微任务)
先微任务后宏任务
举个栗子
<script>
console.log('1')
setTimeout(()=>{
console.log('2')
}, 0)
new Promise((resolve, reject)=> {
console.log('3')
resolve()
}).then(()=> {
console.log('4')
}).catch(()=> {
console.log('5')
})
console.log('6')
let async0 = async function async1() {
console.log('7')
await async2()
console.log('8')
}
async function async2 (){
console.log('9')
}
async0()
</script>
那我们现在根据已知的结论来理一下上面的代码
- 首先执行第一行同步代码,{{ 输出 1 }}
- 第二行是定时器,属于宏任务,放到宏任务队列,稍后执行
- 第五行遇到new Promise先执行,{{ 输出1 3 }}, 此处执行了resolve,说明之后需要执行then中得代码,Promise.then属于微任务,先放到微任务队列
- 第十三行属于同步代码,直接执行 {{ 输出 1 3 6 }}
- 第十四行到二十一行是定义了两个异步函数
- 第二十二行执行了async0,跳到第十四行 {{ 输出 1 3 6 7 }}
- 紧接着执行async2函数,{{ 输出 1 3 6 7 9 }}
- await之后的第十七行代码放到微任务队列
- 主线程任务执行完,现在开始处理微任务队列的任务,Promise.then在微任务队列,第十七行代码也在微任务队列 {{ 输出 1 3 6 7 9 4 8 }}
- 微任务队列执行完之后,开始执行宏任务队列的任务, 定时器再宏任务队列中,{{ 输出 1 3 6 7 9 4 8 2 }}
所以最终输出的结果是 1 3 6 7 9 4 8 2
文章有不足的地方还请各位大佬多多指教,本文中没有介绍promise与async/await之间的关系,之后会出一篇文章来详细介绍。