前言
在说 JavaScript
的执行顺序之前,我们先回答一下以下几组程序的输出结果
第 1 组
const output = (v) => {
console.log(v);
};
setTimeout(() => {
console.log(1);
}, 0);
output(2);
console.log(3);
// 2 3 1
第 2 组
new Promise((resolve) => {
console.log(1);
resolve();
new Promise((resolve) => {
console.log(2);
resolve();
}).then(() => {
console.log(3);
new Promise((resolve) => {
console.log(4);
resolve();
}).then(() => {
console.log(5);
new Promise((resolve) => {
console.log(6);
resolve();
}).then(() => {
console.log(7);
});
});
});
}).then(() => {
console.log(8);
new Promise((resolve) => {
console.log(9);
resolve();
}).then(() => {
console.log(10);
});
});
// 1 2 3 4 8 9 5 6 10 7
看完上面两组案例,是不是会产生这样的疑惑,为什么输出的结果是这样的,别着急,下面我们来详细分析
一、简述
我们都知道 JavaScript
是单线程语言,这表明 JavaScript
同时只能执行一个任务,但执行顺序却并不是自上而下的
这一点,通过上面的案例我们就已经知道了,那么执行规律究竟是什么呢?
要分析 JavaScript
的执行规律,就不得不说一下 JavaScript
的任务分类, JavaScript
分为同步任务
和异步任务
二、同步任务
同步任务:立即执行的任务,解析时遇到同步任务会被主线程立即读取并执行
同步任务:
console.log()
new Promise()
- 直接调用
function()
- …
三、异步任务
异步任务:异步执行的任务,解析时遇到异步任务,直接丢到任务队列中,不会立即读取和执行
异步任务又分为宏任务
和微任务
,执行时优先执行微任务,后执行宏任务
微任务
promise.then()
nextTick()
- …
宏任务
setTimeout()
setInterval()
- …
四、分析
JavaScript
在执行任务之前会先自上而下遍历所有任务,在遍历的过程中如果遇到同步任务则直接执行,执行完成后继续往下遍历,如果遇到异步任务,则放入任务队列中(微任务放入微任务队列,宏任务放入宏任务队列),第一遍遍历完成后,从微任务队列中继续遍历,遇到同步任务立即执行,遇到异步任务继续放入任务队列中,不断的重复这个过程,直到所有任务全部执行完毕,这个不断重复遍历执行的过程,就叫做事件循环
- 第 1 次循环:遍历所有任务,遇到同步任务立即解析并执行,遇到异步任务则放入任务队列中暂不解析
- 第 2 次循环:从任务队列中微任务开始循环,重复第 1 次的过程
- 第 n 次循环:所有同步任务、微任务都执行完毕后,开始循环宏任务,同样重复第 1 次的过程
第 1 组程序 - 解析
const output = (v) => {
console.log(v);
};
// 异步宏任务,放入任务队列,暂不执行
setTimeout(() => {
console.log(1);
}, 0);
// 同步任务,立即执行
output(2);
// 同步任务,立即执行
console.log(3);
第 2 组程序 - 解析
new Promise((resolve) => {
console.log(1);
resolve();
new Promise((resolve) => {
console.log(2);
resolve();
}).then(() => {
console.log(3);
new Promise((resolve) => {
console.log(4);
resolve();
}).then(() => {
console.log(5);
new Promise((resolve) => {
console.log(6);
resolve();
}).then(() => {
console.log(7);
});
});
});
}).then(() => {
console.log(8);
new Promise((resolve) => {
console.log(9);
resolve();
}).then(() => {
console.log(10);
});
});
这个稍微复杂一点,但也很好分析,我们来模拟一下事件循环
第 1 次循环
// 同步任务,立即执行
console.log(1)
// 同步任务,立即执行
new Promise((resolve) => {
console.log(2);
resolve();
})
// 异步任务,放入任务队列
.then(() => {
console.log(3);
new Promise((resolve) => {
console.log(4);
resolve();
}).then(() => {
console.log(5);
new Promise((resolve) => {
console.log(6);
resolve();
}).then(() => {
console.log(7);
});
});
});
// 异步任务,放入任务队列
.then(() => {
console.log(8);
new Promise((resolve) => {
console.log(9);
resolve();
}).then(() => {
console.log(10);
});
});
第一次循环输出结果如下
// 1 2
第 2 次循环
// 同步任务,立即执行
console.log(3);
// 同步任务,立即执行
new Promise((resolve) => {
console.log(4);
resolve();
}
// 异步任务,放入任务队列
.then(() => {
console.log(5);
new Promise((resolve) => {
console.log(6);
resolve();
}).then(() => {
console.log(7);
});
});
// 同步任务,立即执行
console.log(8);
// 同步任务,立即执行
new Promise((resolve) => {
console.log(9);
resolve();
})
// 异步任务,放入任务队列
.then(() => {
console.log(10);
});
第 2 次循环输出结果如下
// 3 4 8 9
第 3 次循环
// 同步任务,立即执行
console.log(5);
// 同步任务,立即执行
console.log(6);
// 异步任务,放入任务队列
.then(() => {
console.log(7);
});
// 同步任务,立即执行
console.log(10);
第 3 次循环输出结果如下
// 5 6 10
第 4 次循环
// 同步任务,立即执行
console.log(7);
第 4 次循环输出结果如下
// 7
所有任务全部执行完毕,循环结束,最终输出结果如下
// 1 2 3 4 8 9 5 6 10 7