同步任务即主线程。同一时间只能做一件事,除此之外不能做其它的事情。
主线程中的任务做完才会去其它的任务队列(异步任务队列)里抓任务来做。
任务队列,我们拆分词语,可以分成任务和队列两个词语。
任务队列就好比去医院排队的过程:
进入队列 | 执行任务 | 移除任务 | 执行完毕 |
---|---|---|---|
排队 | 挂号 | 离开排队的队伍 | 就诊 |
这便是只存在同步任务的情况。
js就是单线程语言,js的代码若不进行特殊处理,其执行顺序一般按编写、调用的顺序执行。
.
.
宏观上的执行顺序
比如秃鹫这类食腐动物,它们只在猎物死亡后才会去进食,在此之前,它们会静静等待其它的捕食者先进食。
即:
种类
异步任务在js中大致为两种:
既然种类不止一个,那么必然会有执行的优先级问题。
我们拿大人与小孩举例子,将吃饭看作为任务:
大人吃饭 | 小孩吃饭 |
---|---|
宏任务 | 微任务 |
大人会饿,但如果小孩饿了,那么我们会优先满足小孩,先让小孩吃饭,然后大人再吃饭。
即:
.
至此,我们可以拥有大致的知识轮廓:
任务队列相当于工作台,同步任务优先于异步任务执行;异步任务中,微任务优先于宏任务执行。
.
首先,我们需要知道:
以下我们以then代表微任务,以setTimeout代表宏任务
Step 1:纯同步
注意以下代码执行结果的顺序:
new Promise((resolve,reject)=>{
console.log('同步执行:promise的构造函数')
})
console.log('同步执行:全局')
//输出结果:
同步执行:promise的构造函数
同步执行:全局
我们可以理解为:
console.log('同步执行:promise的构造函数')
console.log('同步执行:全局')
.
.
Step 2: then微任务 + 同步
我们使用then获取resolve传递的数据:
new Promise((resolve, reject) => {
resolve('异步执行:then方法')
console.log('同步执行:promise的构造函数')
})
.then(
successCallbacks =>console.log(successCallbacks)
)
console.log('同步执行:全局')
// 输出结果:
同步执行:promise的构造函数
同步执行:全局
异步执行:then方法
此时,无论有多少个同步的代码,then方法永远在最后执行。
无论作业留多少,我永远在最后写。
.
.
当我们连续使用then的时候:
new Promise((resolve, reject) => {
resolve('异步执行:then方法')
console.log('同步执行:promise的构造函数')
})
.then(
successCallbacks => console.log(successCallbacks)
)
.then(
() => console.log('then * 2')
)
.then(
() => console.log('then * 3')
)
console.log('同步执行:全局')
//输出结果:
同步执行:promise的构造函数
同步执行:全局
异步执行:then方法
then * 2
then * 3
...
这时我们已经可以简单的对返回值连续进行处理。
.
.
Step 3:setTimeout宏任务 + 同步
setTimeout(() => {
console.log("异步宏任务执行")
}, 0);
console.log('同步执行:全局')
for (let i = 0 ; i < 10000 ; i++){
console.log('n');
}
(这里顺便一提,setTimeout的delay即便设置为0,也实际上被设置为0.004s,即最低为4ms。)
.
.
step 4:同步+异步
当宏任务与微任务同时出现时,微任务优先。大人一般先照顾孩子,再照顾自己。
new Promise((resolve, reject) => {
resolve('异步执行:then方法')
setTimeout(() => {
console.log('异步执行:setTimeout');
}, 0);
console.log('同步执行:promise的构造函数')
})
.then(
successCallbacks => console.log(successCallbacks),
)
console.log('同步执行:全局')
//执行结果:
同步执行:promise的构造函数
同步执行:全局
异步执行:then方法
异步执行:setTimeout
Promise回调中,我们无论怎么变换resolve()/reject()、 setTimeout()、console.log()的代码顺序,执行结果都不会发生变化
.
.
step 5 : 深入理解
查看以下代码:
new Promise((resolve, reject) => {
console.log('同步执行:promise的构造函数')
setTimeout(() => {
resolve('异步执行:then方法')
console.log('异步执行:setTimeout');
}, 0);
})
.then(
successCallbacks => console.log(successCallbacks),
)
console.log('同步执行:全局')
resolve放在了setTimeout里,此时的执行结果:
同步执行:promise的构造函数
同步执行:全局
异步执行:setTimeout
异步执行:then方法
原因是:resolve()想要执行,必须先执行setTimeout()。换句话说,执行setTimeout()之后resolve才能被执行。
当执行setTimeout()的时候,此时同步任务队列已经执行完同步代码,主线程将setTimeout()宏任务放入任务队列中。
此时的resolve相当于在宏任务下的子任务。
在setTimeout()里,语句console.log('异步执行:setTimeout')
相当于setTimeout里的同步代码,
而resolve()则是其异步代码。
拿博客举例子:
我们必须先打开博客的网页,显示出博客的页面才能看博客里的文章:
new Promise((resolve,reject)=>{
打开博客的网页(()=>{
resolve(看博客里的文章)
console.log(显示出博客的页面)
})
})
最后来一个小测验,判断代码执行的顺序:
setTimeout(() => {
setTimeout(() => {
console.log("内层的定时器");
}, 0);
new Promise(resolve => {
console.log("定时器内的Promise");
resolve();
}).then(() => {
console.log("定时器内的then");
});
console.log("最外层的定时器");
}, 0);
new Promise(resolve => {
console.log("全局的Promise");
resolve();
}).then(() => {
console.log("全局的then");
});
console.log("全局的log打印");
.
.
.
答案如下:
全局的log打印
全局的then
定时器内的Promise
最外层的定时器
定时器内的then
内层的定时器