1 event loop 是什么
event loop(事件循环/事件轮询)
- JS是单线程运行的
- 异步要基于回调来实现
- event loop 就是异步回调的实现原理
JS如何执行?
- 从前到后,一行一行执行
- 如果某一行执行报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步
2 event loop 运行过程
过程1:
- 同步代码,一行一行放在Call Stack 执行
- 遇到异步,会先 “记录” 下,放到 Web APIS, 等待时机(定时,网络请求等)
- 时机到了,就移动到 Callback Queue
过程2:
- 如 Call Stack 为空(即同步代码执行完) Event Loop开始工作
- 轮询查找Callback Queue,如有则移动到Call Stack 执行
- 然后继续轮询查找(永动机一样)
3 event loop和DOM事件的关系
- JS是单线程的
- 异步(setTimeout,ajax等)使用回调,基于event loop
- DOM事件也使用回调,基于event loop
注意:如果是直接调用了DOM 事件,不是等待用户点击的话,会先执行
const boxDom = document.querySelector('.box')
console.log(1);
boxDom.addEventListener("click", () => {
console.log(2);
})
boxDom.click() //按照事件轮询机制,异步任务都会在同步任务完成之后再执行,然而结果却是 1 2 3 dom事件的回调没有等待 执行了
console.log(3);
4 什么是宏任务和微任务
- 宏任务:setTimeout,setInterval,Ajax,DOM事件
- 微任务:Promise / async-await
- 微任务执行时机比宏任务要早
// 宏任务macroTask和微任务microTask
console.log(100);
setTimeout(() => {
console.log(200);
});
Promise.reject().catch(() => {
console.log(300);
})
Promise.resolve().then(() => {
console.log(301);
})
console.log(400);
// 结果是: 100 400 300 301 200
5 event loop和 DOM 渲染的关系
- 再次回归一遍event loop的过程
- JS 是单线程的,而且和DOM渲染共用一个线程
- JS执行的时候,得留一些时机供DOM渲染
6 为什么微任务比宏任务更早
- 宏任务:DOM渲染后触发,如setTimeout
- 微任务:DOM渲染前触发,如Promise
- 先演示现象,稍后再追究原理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<!-- DOM渲染 //执行顺序: 4 -->
</body>
<script>
const container = document.getElementById('container')
const p1 = "<p>一段文字1</p>"
const p2 = "<p>一段文字2</p>"
const p3 = "<p>一段文字3</p>"
container.innerHTML = p1
container.innerHTML += p2
container.innerHTML += p3
console.log('length', container.children.length); //执行顺序: 1
alert('本次call stack 结束,DOM结构已更新,但尚未触发渲染') //执行顺序: 2
// 宏任务 DOM渲染之后触发
setTimeout(() => {
const length = container.children.length;
alert(`macro Task 宏任务 ${length}`) //执行顺序: 5
}, 2000);
// 微任务 DOM渲染之前触发
Promise.resolve().then(() => {
const length = container.children.length;
alert(`micro Task 微任务 ${length}`) //执行顺序: 3
})
</script>
</html>
7 微任务和宏任务的根本区别
- 微任务是ES6语法规定的
- 宏任务是由浏览器规定的
- 微任务不会经过Web APIs,因为Promise不是W3C规范
- 微任务执行时机比宏任务要早
执行顺序:
- 执行同步代码,Call Stack 清空
- 执行当前的微任务 队列(micro task queue)
- 尝试DOM渲染
- Event loop 开始启动,执行宏任务队列
8 解答异步问题
// 第一题
Promise.resolve().then(() => {
console.log(1); //前面是resolve,走then里面的逻辑 打印1 ,之后默认返回成功
}).catch(() => {
console.log(2); //catch 不会触发
}).then(() => {
console.log(3); //打印出 3
})
// 结果: 1 3
// 第二题
Promise.resolve().then(() => {
console.log(11); // 前面是resolve 这里可打印出 1
throw new Error('error1') //返回异常
}).catch(() => {
console.log(22); //catch捕获到前面的异常,默认返回成功状态 打印出 2
}).then(() => {
console.log(33); // 前面是成功状态,then可以捕获到 打印出3
})
// 结果为: 11 22 33
// 第三题
Promise.resolve().then(() => {
console.log(111);
throw new Error('error1')
}).catch(() => {
console.log(222);
}).catch(() => {
console.log(333);
})
// 思路同上: 结果为 111 222
// 第四题
// 场景题: async/await语法
async function fn() {
return 100 //返回resolve成功状态,100是参数传递下去
}
(async function () {
const a = fn(); //a是: Promise
const b = await fn(); //b是:100 await相当于then
console.log(a, b);
a.then(res => {
console.log(res); // 100
})
})()
// 第五题
// 下面执行完毕,打印出哪些内容?
(async function () {
console.log('start'); // start
const a = await 100
console.log("a", a); // 成功状态的promise,传了个 100
const b = await Promise.resolve(200)
console.log('b', b); // 200
const c = await Promise.reject(300) //await相当于成功,等待不到一个失败状态的promise
console.log('c', c); //报错
console.log("end"); //前面报错,后面的代码不执行
})()
// 第六题
// 综合题
async function async1() {
console.log('async1 start'); // 执行顺序:2
await async2();
// await 后面作为回调内容 ==> 微任务
console.log('async1 end'); //执行顺序:6
}
async function async2() {
console.log('async2'); //执行顺序:3
}
console.log('script start'); // 执行顺序:1.先执行同步任务
setTimeout(function () { // 宏任务
console.log('setTimeout'); //执行顺序:8.宏任务在微任务后面执行
}, 0)
async1()
// 初始化Promise 传入的函数会立刻执行
new Promise(function (resolve) {
console.log('promise1'); //执行顺序:4
resolve();
}).then(() => { //微任务
console.log('promise2'); //执行顺序:7
})
console.log('script end'); // 执行顺序:5.执行同步任务
// 同步代码执行完毕(event loop -- call stack 被清空)==》 执行微任务 ==》 尝试触发DOM渲染 ==》 触发event loop 执行宏任务
9 总结
- async/await 解决了异步回调地狱,是一个很香的语法糖
- async/await和promise的关系
执行async函数,返回的是Promise对象 await相当于Promise的then try...catch可捕获异常,相当于Promise的catch