简介

首先,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');
}

// asyncF is equivalent to f!
async function asyncF() {
return 'TEST';
}

await

await关键字只能在async函数中使用,用于等待一个Promise对象的状态改变,如果Promise对象的状态变为resolved,则返回Promise对象的值,如果Promise对象的状态变为rejected,则抛出一个异常

1
2
3
4
5
6
async function f(){
// 等同于
// return 123
return await 123
}
f().then(v => console.log(v)) // 123

不管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"