事件循环(event loop)
进程和线程简述
JavaScript的单线程特性导致了其特殊的事件循环机制
-
进程是 cpu 资源分配的最小单位(是能拥有资源和独立运行的最小单位)
-
线程是 cpu 调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
浏览器是多进程的
浏览器是多进程的,浏览器每一个 tab 标签都代表一个独立的进程,其中浏览器渲染进程(浏览器内核)属于浏览器多进程中的一种,主要负责页面渲染,脚本执行,事件处理等
其包含的线程有:GUI 渲染线程(负责渲染页面,解析 HTML,CSS 构成 DOM 树)、JS 引擎线程、事件触发线程、定时器触发线程、http 请求线程等主要线程
JavaScript 是单线程的
为什么JavaScript 不是多线程 ?
作为浏览器脚本语言,JavaScript的主要用途就是用户的互动和DOM操作,如果JavaScript使用多线程处理,确实一定程度上能提高效率,但也需要承受多线程带来的线程切换,CPU轮转和同步锁等多种可能出现的问题,这对于JavaScript来说是得不偿失,可以说单线程更加适合JavaScript
关于HTML5 提出 Web Worker 标准,确实允许了JavaScript 脚本创建多个线程,但子线程受主线程控制,且不能操作 DOM。并未改变JavaScript的单线程本质
任务队列(event queue)
任务队列可以分为同步任务和异步任务
同步任务立即执行
异步任务会通过任务队列的机制排队执行
异步任务的宏任务和微任务
宏任务:script整体代码、setTimeOut、setInterval、setImmediate、I/O、UI rendering
微任务:promise、Object observe、MutationObserver
优先级:process.nextTick>promise.then>setTimeOut>setImmediate
任务执行顺序
1.执行整体代码作为宏任务
2.选择宏任务队列中的宏任务时会选择可以立即执行的(setTimeOut中的时间参数会影响宏任务执行顺序)
第一次宏任务执行:
代码从上至下
看到宏任务就加入宏任务队列(暂缓执行宏任务的内部代码)
看到微任务就加入微任务队列(暂缓执行微任务代码)
结束第一次宏任务
执行微任务队列中所有微任务
微任务中看到微任务会排在微任务内部普通代码后执行
微任务中看到宏任务就加入宏任务队列
结束第一次微任务队列
第二次宏任务执行
看到宏任务就加入宏任务队列
看到微任务就加入微任务队列
第二次宏任务结束
执行第二次微任务队列
与第一次相同,循环执行
问题的关键就在于微任务中执行微任务的方案是和宏任务中执行微任务不同的
实例
console.log('宏任务1')
setTimeout(()=>{
console.log('宏任务2')
new Promise((resolve)=>{
resolve()
}).then(()=>{
console.log('宏任务2下微任务')
new Promise((resolve)=>{
resolve()
}).then(()=>{
console.log('宏任务2下微任务的微任务')
})
console.log('宏任务2下微任务的普通代码')
})
setTimeout(()=>{
console.log('宏任务2下宏任务')
})
})
new Promise((resolve)=>{
resolve()
}).then(()=>{
console.log('宏任务1下微任务')
new Promise((resolve)=>{
resolve()
}).then(()=>{
console.log('宏任务1下微任务的微任务1')
})
setTimeout(()=>{
console.log('宏任务1下微任务下宏任务')
})
console.log('宏任务1下微任务的普通代码')
new Promise((resolve)=>{
resolve()
}).then(()=>{
console.log('宏任务1下微任务的微任务2')
})
})
执行顺序为
小结
-
经过试验证明,宏任务执行队列的顺序并不是按照代码从上至下,而是从可以立即执行的红任务中按队列顺序执行,如果一个宏任务setTimeOut的时间为50ms,其他宏任务为0,就算代码行中的50ms宏任务在其他任务之前,也会在最后执行。
**注:**当setTimeOut中ms参数足够小的时候,比如1ms,时间短到按正常任务队列来判断他可以立即执行的话,则仍然按照任务队列正常执行
-
微任务中跟随微任务为特殊情况,跟随的微任务会在当前微任务的普通代码执行完毕后立即执行,而不是排入下一次微任务队列。