事件循环机制
首先,JavaScript
是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环。
在JavaScript
中,所有的任务都可以分为:
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
- 异步任务:异步执行的任务,比如
ajax
网络请求,setTimeout
定时函数等
根据上图解读:
- 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数
- 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
- 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
- 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
宏任务和微任务
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
按照上述 图来分析这段代码,可以得到执行步骤:
- console.log('script start'); 同步任务,主线程中执行, script start'
- setTimeout() 异步任务,放到eventTable, 在0 秒后 将回调 推入到 event queue(异步队列)中
- .then 异步任务 放到 eventTable, 回调函数 入 异步队列
- .then 异步任务 放到 eventTable, 回调函数 入 异步队列
- console.log('script end'); 同步任务,主线程中执行, script end'
==> 输出结果应该是 script start script end setTimeout promise1 promise2
==> 但是在浏览器 里 输出的是 script start script end promise1 promise2 setTimeout
宏任务(macro-task)
macro-task
大概包括:
- script(整体代码)
- setTimeout
- setInterval
- setImmediate / I/O (node.js)
- UI rendering (UI 渲染)
微任务(micro-task)
micro-task
大概包括:
- process.nextTick(Node.js)
- Promise.then
- MutationObserver (html5新特性)
宏任务,微任务的关系如图所示
按照这个流程,它的执行机制是:
- 存在微任务的话,那么就执行所有的微任务
- 微任务都执行完之后,执行第一个宏任务,
- 循环 1, 2
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// script start script end promise1 promise2 setTimeout
流程如下:
- console.log('script start'); 同步任务,主线程中执行, script start'
- 定时器,宏任务队列 后面执行
- .then 微任务,微任务队列,后面执行
- console.log('script end'); script end
- 到 script end 本轮 同步代码 执行结束, 执行本次宏任务里所有微任务两个.then
- 微结束之后 再 开启下一个 宏任务
<script>
console.log('start1')
setTimeout(() => console.log('timer1'), 0)
new Promise((resolve, reject) => {
console.log('p1')
resolve()
}).then(() => {
console.log('then1')
})
console.log('end1')
</script>
<!-- 脚本 2 -->
<script>
console.log('start2')
setTimeout(() => console.log('timer2'), 0)
new Promise((resolve, reject) => {
console.log('p2')
resolve()
}).then(() => {
console.log('then2')
})
console.log('end2')
</script>
- 两个script 标签 两个宏任务 宏任务队列 里
- 现在微任务是空的 可以理解成上一个执行结束了,开启一个 新宏任务
- console.log('start1') 同步 主线程 打出start1
- setTimeout 异步 宏任务队列 里===> 现在宏任务队列已经有两个宏任务了 再脚本2 之后
- new Promise() 同步 打出p1
- .then 是微任务 放 微任务队列
- console.log('end1') 同步 主线程 打出end1
start1 p1 end1 then1
思考一下: 下一个宏任务是谁??? 脚本2
- console.log('start2') 同步 主线程 打出start2
- setTimeout 异步 宏任务队列 里
- new Promise() 同步 打出p2
- .then 是微任务 放 微任务队列
- console.log('end2') 同步 主线程 打出end2
start2 p2 end2 then2 timer1 timer2
async与await
async
async
是异步的意思,await
则可以理解为 async wait
。所以可以理解async
就是用来声明一个异步方法,而 await
是用来等待异步方法执行
async
函数返回一个 Promise 对象。
async function f() {
return 'hello world';
}
function f() {
return Promise.resolve('hello world');
}
await 命令
正常情况下,await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
async function f(){
// 等同于
// return 123
return await 123
}
f().then(v => console.log(v)) // 12
不管await
后面跟着的是什么,await
都会阻塞后面的代码
async function fn1 (){
console.log(1)
await fn2()
console.log(2) // 阻塞
}
async function fn2 (){
console.log('fn2')
}
fn1()
console.log(3)
上面的例子中,await
会阻塞下面的代码(即加入微任务队列),先执行 async
外面的同步代码,同步代码执行完,再回到 async
函数中,再执行之前阻塞的代码
所以上述输出结果为:1
,fn2
,3
,2