简介
首先,JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环
任务分类:
在JavaScript中,任务被分为两种类型:同步任务和异步任务
同步任务
同步任务指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。例如,console.log()就是一个同步任务
异步任务
异步任务指的是不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
例如,setTimeout()、Promise().then()就是异步任务
任务执行
上面以及简单的介绍了两种任务的执行顺序,但值得注意的是,有些时候会出现较为复杂的嵌套问题,比如下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| console.log(1)
new Promise((resolve, reject)=>{ console.log('new Promise') resolve() }).then(()=>{ console.log('then') setTimeout(()=>{ console.log(4) }, 0)
})
setTimeout(()=>{ console.log(2) }, 0)
console.log(3)
|
1 2 3 4 5 6
| > 1 > "new Promise" > 3 > "then" > 2 > 4
|
想必大家会很清楚第一遍会先执行同步任务会输出1
new Promise
3
然后会执行最表层的异步任务,会输出then
2
,可能大家会比较疑惑为什么不是先输出2
在输出then
,这里我们先保留这个问题
最后执行setTimeout
的回调函数,输出4
但是这里有一个问题,为什么2
会在4
之前输出呢?
官方的解释或许比较抽象,我们简单来看的话可以理解为当一个异步函数fn1也就是这里面的 setTimeout(()=>{ console.log(4)}, 0)
和一个同步函fn2数
也就是console.log('then')
被另一个异步函数fn3包裹时,fn1的优先级会降低,而fn2的优先级不变,而此时存在异步函数fn4和异步函数fn3处于同一优先
级,所以fn4索包裹的fn5console.log(2)
和fn2处于同一优先级,所以会先执行fn2和fn5,最后执行fn1
呃呃呃呃,绕死了,只能相信大家的悟性了
宏任务与微任务
上述提到了大家会比较疑惑为什么不是先输出2
在输出then
,这里是因为异步任务也可以分为宏任务与微任务两种任务类型:
宏任务
宏任务包括:setTimeout、setInterval、setImmediate、I/O、UI渲染
微任务
微任务包括:Promise.then(主要)、process.nextTick、MutationObserver
执行顺序
当执行栈(主线程)为空时,会先执行微任务,再执行宏任务,当微任务执行完毕后,会执行宏任务中的第一个任务,然后再执行微任务,如此往复循环
注意的是微任务的优先级高于宏任务,所以会先执行微任务,再执行宏任务
所以返回去看就会先输出then
再输出2
代码示例(大家自己意会一下吧)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| console.log(1)
new Promise((resolve, reject)=>{ console.log('new Promise') resolve() }).then(()=>{ console.log('then') setTimeout(()=>{ console.log(4) }, 0)
})
setTimeout(()=>{ console.log(2) setTimeout(()=>{ console.log(5) }, 0) }, 0)
console.log(3)
|
1 2 3 4 5 6 7
| > 1 > "new Promise" > 3 > "then" > 2 > 4 > 5
|
小结
事件循环的概念比较难以描述但理解起来其实并不复杂,这里在写这篇文章的时候就觉的写的有点烂,希望大家能理解
重要的是大家可以自行修改上述代码看看运行结果,这样会加深对事件循环的理解,理解起来也更容易
( ͡ ͡° ͜ ʖ ͡ ͡°) 大家加油~~
async和await
async和await是ES7中新增的两种语法糖,用于处理异步操作,其本质也是基于Promise实现的
async
async函数返回一个Promise对象,可以使用then方法添加回调函数。下面两种方法是等效的
1 2 3 4 5 6 7 8 9
| function f() { return Promise.resolve('TEST'); }
async function asyncF() { return 'TEST'; }
|
await
await关键字只能在async函数中使用,用于等待一个Promise对象的状态改变,如果Promise对象的状态变为resolved,则返回Promise对象的值,如果Promise对象的状态变为rejected,则抛出一个异常
1 2 3 4 5 6
| async function f(){ return await 123 } f().then(v => console.log(v))
|
不管await后面跟着的是什么,await都会阻塞后面的代码
1 2 3 4 5 6 7 8 9 10 11 12
| 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
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); }
async function async2() { console.log('async2'); }
console.log('script start');
setTimeout(function() { console.log('setTimeout'); }, 0);
async1();
new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); });
console.log('script end');
|
1 2 3 4 5 6 7 8
| > "script start" > "async1 start" > "async2" > "promise1" > "script end" > "async1 end" > "promise2" > "setTimeout"
|
大礼包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| setTimeout(function () { console.log(1) }, 0); Promise.resolve().then(function () { console.log(2); }).then(function () { console.log(3); }); new Promise(function (resolve) { console.log(4); for (var i = 0; i < 10000; i++) { if (i == 9999) { resolve(); } } console.log(5); }).then(function () { console.log(6); }); console.log(7);
|
1 2 3 4 5 6 7
| > "4" > "5" > "7" > "2" > "6" > "3" > "1"
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| setTimeout(function () { console.log(1) }, 0); Promise.resolve().then(function () { console.log(2); setTimeout(function () { console.log(8) }, 0); }).then(function () { console.log(3); }); new Promise(function (resolve) { console.log(4); for (var i = 0; i < 10000; i++) { if (i == 9999) { resolve(); } } console.log(5); }).then(function () { console.log(6); }); console.log(7);
|
1 2 3 4 5 6 7 8
| > "4" > "5" > "7" > "2" > "6" > "3" > "1" > "8"
|