js是单线程
与JS的用途有关,js主要是用来实现用户与浏览器交互的浏览器脚本语言,如果是多线程,会带来很多复杂的同步问题,比如:不同的线程同时操作一个dom元素的问题。为了避免复杂性,将js设计为单线程
为了利用多核CPU的计算能力,h5提出Web Worker标准,允许JS脚本创建多个线程,但是子线程完全受主线程的控制,并且不能操作DOM。(因此这个标准并没有改变JS是单线程的本质)
任务队列
在任务队列中,分为两大类:同步任务、异步任务
执行栈
- 所有的同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,还存在着一个“任务队列”,当异步任务有了结果,就在“任务队列”中放置一个事件
- 一旦“执行栈”中的所有同步任务执行完毕,系统回去读取“任务队列”,看其中的事件对应着的异步任务,会进入到执行栈开始执行。其中异步任务又分为了宏任务和微任务,宏任务和微任务再按照一定的规则执行
- 主线程不断重复上面所述的三步,称为事件回滚(Event Loop)
宏队列
用来保存待执行的宏任务,如:定时器回调、DOM事件回调、ajax回调
微队列
用来保存待执行的微任务,如:promise回调、MutationObserver 回调、nextTick
js执行顺序
先执行所有初始化同步任务代码(可以看做是一个宏任务?),在执行的时候会挂起一些后执行的宏任务和微任务,执行完成后会执行被队列挂起的微任务,之后是宏任务(微任务的优先级高于宏任务)
setTimeout(() => { //立即放入宏队列
console.log('timeout callback1()')
Promise.resolve(3).then(
value => { //立即放入微队列
console.log('Promise onResolved3()', value)
}
)
}, 0)
setTimeout(() => { //立即放入宏队列
console.log('timeout callback2()')
}, 0)
Promise.resolve(1).then(
value => { //立即放入微队列
console.log('Promise onResolved1()', value)
setTimeout(() => {
console.log('timeout callback3()', value)
}, 0)
}
)
Promise.resolve(2).then(
value => { //立即放入微队列
console.log('Promise onResolved2()', value)
}
)
// Promise onResolved1() 1
// Promise onResolved2() 2
// timeout callback1()
// Promise onResolved3() 3
// timeout callback2()
// timeout callback3() 1