导航
如上图,在任务管理器的进程一栏里,有道词典和有道云笔记就是进程,而在进程下又有着多个执行不同任务的线程。
因为不能一边渲染内容,一边JS在修改DOM。
所以
<div>Front of you</div>
<script>while(true) {}</script> <!-- JS一直在执行,不空闲 -->
<div>End of you</div> <!-- 页面一直卡死,不渲染任何东西 -->
<div>Front of you</div>
<script>
setTimeout(() => {
document.getElementByTagName('div')[0].innerText = 'Front of me';
}, 1000);
</script> <!-- JS执行完后,空闲 -->
<div>End of you</div> <!-- 页面渲染 Front of you 和 End of you,1s后改为 Front of me -->
<script>...</script>
这段脚本整体,也是个宏任务)简单记顺序就是:
同步代码 → 清空所有微任务 → 渲染 → 取宏任务队列中一个出来执行
document.body.style.backgroundColor = 'orange';
console.log(1);
setTimeout(()=> { // setTimeout1
document.body.style.backgroundColor = 'green';
console.log(2);
}, 100);
Promise.resolve(3).then(num => { // Promise.then1
document.body.style.backgroundColor = 'purple';
console.log(num);
});
console.log(4);
分析:
// 宏任务 setTimeout
// 微任务 Promise.then
/**
* 过程:
* 设置 body.bgColor = 'orange'; -> 没渲染
* 打印1
* (把 setTimeout 执行,把回调放进 宏任务队列)
* 同步执行 Promise.resolve(3) 把 then 回调放进 微任务队列
* 打印4
* 同步任务执行栈代码执行完毕
* 先清空微任务队列, 执行 Promise.then
* 设置 body.bgColor = 'purple'; -> 没渲染
* 打印 3
* GUI渲染, 把 body.bgColor 渲染成了 purple
* 取宏任务队列中 setTimeout cb ,等到 100ms 以后放进JS引擎执行栈中执行
* 设置 body.bgColor = 'green' -> 没渲染
* 打印 2
* 同步代码执行栈中已经没有任务了
* 微任务也没任务了
* 再GUI渲染
* 把 body.bgColor = 'green' -> 此时渲染了
* /
Promise.resolve().then(() => { // then1
console.log('p1');
setTimeout(() => { // setTimeout2
console.log('s2');
}, 0);
});
setTimeout(() => { // setTimeout1
console.log('s1');
Promise.resolve().then(() => { // then2
console.log('p2');
});
});
分析:
/**
* 宏任务: setTimeout1 setTimeout2
* 微任务: then1 then2
* 打印: p1 s1 p2 s2
*/
Promise.resolve().then(() => { // then1
console.log('p1');
setTimeout(() => { // setTimeout2
console.log('s2-1');
}, 0);
setTimeout(() => { // setTimeout3
console.log('s2-2');
}, 0);
});
setTimeout(() => { // setTimeout1
console.log('s1');
Promise.resolve().then(() => { // then2
console.log('p2-1');
}).then(() => { // then3
console.log('p2-2');
});
});
分析:
1. 执行 then1, 把 then1 放进微任务队列
// 此刻微任务队列: then1
2. 执行 setTimeout1, 把 setTimeout1 放进 宏任务队列
// 此刻宏任务队列: setTimeout1
到此, JS引擎执行栈代码已经全部执行完毕
3. 开始清空微任务队列
执行 then1
【打印 p1】
把 setTimeout2 和 setTimeout3 放进宏任务队列
// 此刻宏任务队列: setTimeout1、setTimeout2、setTimeout3
微任务队列全部执行完毕, 移除 then1
// 此刻微任务队列: 空
4. 开始执行宏任务
取出宏任务中第一个 setTimeout1 任务执行
【打印 s1】
把 then2、then3 放入微任务队列
// 此刻微任务队列: then2、then3
宏任务 setTimeout1 执行完毕, 移除 setTimeout1 任务
// 此刻宏任务队列: setTimeout2、setTimeout3
进入下次循环
5. 开始清空微任务队列
执行 then2
【打印 p2-1】
执行 then3
【打印 p2-2】
微任务队列清空完毕, 清除 then2、then3
// 此刻微任务队列: 空
6. 开始执行宏任务
取出宏任务中第一个 setTimeout2 执行
【打印 s2-1】
宏任务 setTimeout2 执行完毕, 移除 setTimeout2 任务
// 此刻宏任务队列: setTimeout3
7. JS引擎执行栈中没有代码执行, 微任务队列也没有任务, 再次从宏任务队列中取任务执行
取出宏任务中最后一个任务 setTimeout3 执行
【打印 s2-2】
宏任务队列也清空了
所以最终打印顺序为:
p1
s1
p2-1
p2-2
s2-1
s2-2
console.log(1);
setTimeout(() => { // setTimeout1
console.log(2);
}, 10);
new Promise(function(resolve, reject) {
console.log(3);
resolve('');
console.log(4);
}).then(res => { // then1
console.log(5);
});
console.log(6);
分析:
1. 【先打印 1】
2. 把 setTimeout1 放进 宏任务队列
// 此刻宏任务队列: [setTimeout1]
3. new Promise 中 function 立即同步执行(executor是个同步代码)
【打印3】
执行resolve,把 then1 放进 微任务队列
// 此刻微任务队列: [then1]
【打印4】
function 执行完毕
4. 执行同步log6
【打印6】
5. 开始清空微任务队列
执行 then1
【打印5】
6. 开始执行宏任务队列中的第一个
等待10ms, 执行 setTimeout1 cb
【打印2】
最终打印顺序:
1
3
4
6
5
2
改造:
⭐️ 把 executor 中的 resolve 删掉
console.log(1);
setTimeout(() => { // setTimeout1
console.log(2);
}, 10);
new Promise(function(resolve, reject) {
console.log(3);
// resolve('');
console.log(4);
}).then(res => { // then1
console.log(5);
});
console.log(6);
// 分析
resolve 不执行,导致 then1 不会被调用, 所以 then1 不会加入到微任务队列
所以最终打印为
1
3
4
6
2
继续改造,把 resolve 改为 reject:
console.log(1);
setTimeout(() => { // setTimeout1
console.log(2);
}, 10);
new Promise(function(resolve, reject) {
console.log(3);
reject('');
console.log(4);
}).then(res => { // then1
console.log(5);
});
console.log(6);
// 分析
then1会被放进微任务队列,但 then 的第二个回调没指定,所以会报错
打印:
接着改造,我们把 reject 回调指定一下:
console.log(1);
setTimeout(() => { // setTimeout1
console.log(2);
}, 10);
new Promise(function(resolve, reject) {
console.log(3);
reject('');
console.log(4);
}).then(res => { // then1
console.log(5);
}, err => {
console.log('err', 5);
});
console.log(6);
// 分析
这次指定了 错误捕获,所以打印 'err 5'
⭐️ 再次改造
console.log(1);
setTimeout(() => { // setTimeout1
Promise.resolve().then(() => { // then2
console.log(2);
});
}, 10);
new Promise(function(resolve, reject) {
console.log(3);
resolve('');
console.log(4);
}).then(res => { // then1
setTimeout(() => { // setTimeout2
console.log(5);
}, 0);
});
console.log(6);
// 分析
这次要注意一个点,setTimeout1 是延迟 10ms 执行的
所以 setTimeout2 的执行会先于 setTimeout1
导致 then2 后于 setTimeout2 执行
最终打印
1
3
4
6
5
2
let res = function() {
console.log(1);
return new Promise((resolve, reject) => {
console.log(2);
resolve(4);
})
}
new Promise(async (resolve, reject) => {
console.log(3);
let test = await res();
console.log(test);
});
console.log(5);
new Promise((resolve, reject) => {
console.log(6);
});
console.log(7);
分析:
// async 函数 默认返回一个 promise 实例
// await 必须存在于 async 函数中
// await test() 产出一个值,就相当于 yield 产出一个值
// test 如果返回一个 promise 对象,
// 那么 await test() 就会去调用 test() 执行以后的 then(res => {}) 方法, 拿到 res 结果
let res = function() {
console.log(1);
return new Promise((resolve, reject) => {
console.log(2);
resolve(4);
})
}
new Promise(async (resolve, reject) => {
console.log(3);
let test = await res(); // then1
console.log(test);
});
console.log(5);
new Promise((resolve, reject) => {
console.log(6);
});
console.log(7);
1. 定义一个 function 赋值给 res
2. 执行 new Promise 中的 function
不要看是个 async 就觉得是异步执行,此 function 就是个立即执行的 executor
只不过 async 表示此 function 最终要返回一个 promise 对象而已
【打印3】
await res() 相当于调用 res().then()
立马执行 res 函数
【打印1】
执行 res 中的 new Promise
【打印2】
resolve(4) 后把 res().then() 放入微任务队列
// 此刻微任务队列: [then1]
3.【打印5】
4. 执行 new Promise
【打印6】
5.【打印7】
6. 执行栈同步代码全部执行完了,开始执行所有微任务
then() 执行
【打印4】
最终打印结果:
3
1
2
5
6
7
4
改造:
let res = function() {
console.log(1);
return new Promise((resolve, reject) => {
setTimeout(() => {
new Promise(resolve => {
console.log(2);
setTimeout(() => {
console.log(3);
});
});
}, 0);
resolve(5);
})
}
new Promise(async (resolve, reject) => {
setTimeout(() => {
Promise.resolve().then(() => {
console.log(4);
});
}, 0);
let test = await res();
console.log(test);
});
setTimeout(() => {
console.log(6);
}, 0);
new Promise((resolve, reject) => {
setTimeout(() => {
console.log(7);
}, 0);
});
console.log(8);
分析:
let res = function() {
console.log(1);
return new Promise((resolve, reject) => {
setTimeout(() => { // setTimeout2
new Promise(resolve => {
console.log(2);
setTimeout(() => { // setTimeout5
console.log(3);
});
});
}, 0);
resolve(5);
})
}
new Promise(async (resolve, reject) => {
setTimeout(() => { // setTimeout1
Promise.resolve().then(() => { // then2(4)
console.log(4);
});
}, 0);
let test = await res(); // then1(5)
console.log(test);
});
setTimeout(() => { // setTimeout3
console.log(6);
}, 0);
new Promise((resolve, reject) => {
setTimeout(() => { // setTimeout4
console.log(7);
}, 0);
});
console.log(8);
/**
* 宏任务: setTimeout1 setTimeout2 setTimeout3 setTimeout4 setTimeout5
* 微任务: then1(5) then2(4)
* 打印: 1 8 5 4 2 6 7 3
*/
<button id="btn">click</button>
<script>
const oBtn = document.getElementById('btn');
oBtn.addEventListener('click', () => {
console.log(1);
Promise.resolve('m1').then(str => { // then1
console.log(str);
});
}, false)
oBtn.addEventListener('click', () => {
console.log(2);
Promise.resolve('m2').then(str => { // then2
console.log(str);
})
}, false)
oBtn.click();
</script>
分析:
addEventListener 中的回调算宏任务, oBtn.click() 之后 会立即按代码顺序调用 两个回调
相当于把这俩回调当做同步在依次执行
类似于下面代码
function handler1() { // cb1
console.log(1);
Promise.resolve('m1').then(str => { // then1
console.log(str);
});
}
function handler2() { // cb2
console.log(2);
Promise.resolve('m2').then(str => { // then2
console.log(str);
})
};
handler1();
handler2();
所以
1. 先执行第一个 click 回调
【打印1】
把 then1 放进 微任务队列
// 此刻微任务队列: [then1]
2. 再执行第二个 click 回调
【打印2】
把 then2 放进 微任务队列
// 此刻微任务队列: [then1, then2]
3. 执行栈中同步代码都执行完毕,开始执行所有微任务
执行 then1
【打印m1】
执行 then2
【打印m2】
// 最终打印:
1
2
m1
m2
但如果不是js主动触发 click 事件,而是让用户触发 click 事件,情况就不同了
<button id="btn">click</button>
<script>
const oBtn = document.getElementById('btn');
oBtn.addEventListener('click', () => {
console.log(1);
Promise.resolve('m1').then(str => { // then1
console.log(str);
});
}, false)
oBtn.addEventListener('click', () => {
console.log(2);
Promise.resolve('m2').then(str => { // then2
console.log(str);
})
}, false)
</script>
分析:
此种情况,就要看做是走了两次事件循环,
把click回调当做两个宏任务依次放进宏任务队列,一个个取出来执行
1. 用户点击按钮
2. 触发第一个 click 回调
【打印1】
把 then1 添加进微任务队列
// 此刻微任务队列: [then1]
3. 清空微任务队列
【打印m1】
// 此刻微任务队列: []
4. 触发第二个 click 回调
【打印2】
把 then2 添加进微任务队列
// 此刻微任务队列: [then2]
5. 清空微任务队列
【打印m2】
最终顺序:
1
m1
2
m2
改造:
const oBtn = document.getElementById('btn');
oBtn.addEventListener('click', () => { // cb1
setTimeout(() => { // setTimeout1
console.log('1');
}, 0);
Promise.resolve('m1').then(str => { // then1
console.log(str);
});
}, false)
oBtn.addEventListener('click', () => { // cb2
setTimeout(() => { // setTimeout2
console.log('2');
}, 0);
Promise.resolve('m2').then(str => { // then2
console.log(str);
})
}, false)
/**
* 宏任务: cb1 cb2 setTimeout1 setTimeout2
* 微任务: then1(m1) then2(m2)
* 执行: m1 m2 1 2
*/
换成 handler1、handler2:
const oBtn = document.getElementById('btn');
const handler1 = function() {
setTimeout(() => { // setTimeout1
console.log('1');
}, 0);
Promise.resolve('m1').then(str => { // then1
console.log(str);
});
}
const handler2 = function() {
setTimeout(() => { // setTimeout2
console.log('2');
}, 0);
Promise.resolve('m2').then(str => { // then2
console.log(str);
})
}
handler1()
handler2();
/**
* 宏任务: setTimeout1 setTimeout2
* 微任务: then1 then2
* 执行: m1 m2 1 2
*/