前言:大家好,我是忆白,本文根据慕课网双越老师《一天时间迅速准备前端面试 快速构建初级前端知识体系》课程整理的面试常考题目以及相应知识扩展,持续更新。
如果你觉得自己异步相关知识已经掌握了,可以直接跳到最后看面试场景题。
1. 同步和异步
- 异步基于 JS 是单线程语言,只能同时做一件事
- JS 和 DOM 渲染共用同一个线程,因为 JS 可以修改 DOM 结构
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
2. 使用Promise加载一张图片
function loadImg(src) {
return new Promise((resolve, reject) => {
const img = document.createElement('img')
// 图片加载成功的回调
img.onload = () => {
resolve(img);
}
// 图片加载失败的回调
img.onerror = () => {
const err = new Error(`图片加载失败 ${src}`);
reject(err);
}
img.src = src;
})
}
3. 异步应用场景
- 网络请求,如
ajax图片加载 - 定时任务,如
setTimeout
4. Event loop的执行过程
- 同步代码,一行一行放在 Call Stack 执行
- 遇到异步,会先“记录下”,等待时机
- 时机到了,就移到 Callback Queue
- 如果 Call Stack 为空(即同步代码执行完)
- 执行当前微任务队列中的微任务(宏任务和微任务区别后面有讲到)
- 尝试DOM渲染(如果DOM结构改变)
- Event Loop开始工作
- 轮询查找 Callback Queue,如果有则移动到 Call Stack 执行
- 然后继续轮询查找
5. 什么是宏任务,什么是微任务?
异步任务分为两种,一种宏任务,一种微任务,分别位于两个任务队列,微任务的执行实际比宏任务要早。至于为什么,先了解以下event loop 和 DOM 渲染的关系。
event loop 和 DOM 渲染 的关系
- 每次 Call Stack 清空(即每次轮询结束),即同步任务执行完成
- 都是 DOM 重新渲染的机会,都会先尝试DOM渲染,如果DOM结构有改变则重新渲染
- 然后再去触发下一次 Event Loop
宏任务和微任务的区别,执行时机与DOM渲染的关系
- 宏任务:DOM 渲染后触发,如
setTimeout - 微任务:DOM 渲染前触发,如
Promise - 根本区别:为任务是 ES6 语法规定的,宏任务是由浏览器规定的
可以通过alert阻断代码执行,来验证微任务、宏任务与DOM渲染的关系:
<div id="container"></div>
<script>
const div = document.getElementById('container');
div.innerHTML = `<p>一段文字</p>
<p>一段文字</p>
<p>一段文字</p>`
Promise.resolve().then(() => {
console.log(111);
alert('执行微任务,此时可以看到页面没有发生渲染');
})
setTimeout(() => {
console.log(222);
alert('执行宏任务,此时可以看到页面DOM结构已经重新渲染');
}, 0);
</script>
宏任务有哪些?微任务有哪些?
- 宏任务:setTimeout、setInterval、Ajax、DOM事件
- 微任务:Promise,async/await
6. Promise有哪三种状态?如何变化?
三种状态
- pending 过程中
- resolved(fulfilled) 成功
- rejected 失败
- 状态变化不可逆,一旦改变一次,则无法再发生改变。
如何改变?
-
执行resolve()由
pedding改变为resolved -
执行reject()由
pedding改变为rejected -
遇到错误抛出由
pedding改变为rejected -
Promise.then()方法中- return非Promise对象,返回成功的Promise成功的值为return的值(
pendding=>resolved) - return一个Promise对象,则返回的Promise状态和return的Promise一样,值也一样。
- 抛出错误,返回失败的Promise,值为抛出错误内容(
pending=>rejected)
- return非Promise对象,返回成功的Promise成功的值为return的值(
-
Promise.catch()方法实际上是语法糖,相当于Promise.then(空, err=>{}),因此catch返回的Promise状态和then()一样 -
Promise.all()方法中- 如果传入的Promise数组状态都为成功,则返回成功的Promise,值为所有Promise成功值组成的数组。
- 如果传入的Promise数组只要有一个失败,则返回失败的Promise,值为第一个状态变为失败的Promise的值。
-
Promise.race()方法中,取决于传入Promise数组中第一个改变状态的Promise,- 这个Promise如果成功,则race()返回成功的Promise。
- 如果失败,则race()返回失败的Promise
- 成功或失败的值和这个改变状态的Promise相同。
7. async/await
-
由异步回调的 callback hell(回调地狱) 引出了Promise
-
但Promise的then和catch的链式调用,依然是基于回调函数的
-
async/await是同步语法,彻底消灭回调函数 -
async函数返回一个Promise对象,返回的规则同then()方法:
- return 非Promise值,返回成功的值,成功的值为return 的值
- return Promise值,返回的Promise状态由return的Promise决定,成功或失败的值相同
- 抛出错误,返回失败的Promise,值为抛出的错误
-
await返回值的情况根据其后面语句执行结果决定:
- 后面的语句执行结果如果是成功的Promise,await语句返回其成功的值(类似
Promise.then()方法) - 后面的语句执行结果如果是失败的Promise,则await语句抛出错误,可以使用try...catch...捕获
- 后面的语句执行结果如果是非Promise值,则await语句直接返回该值
- 后面的语句执行结果如果是成功的Promise,await语句返回其成功的值(类似
-
async函数调用时,是同步执行的 -
await语句以下的所有代码,都可以看作是一个callback回调里的内容,即异步,微任务 -
async/await只是一个语法糖,异步的本质还是回调函数
场景题
做完之后,可以右滑,查看注释的答案。
1. setTimeout
console.log(1);
setTimeout(() => {
console.log(2);
}, 1000);
console.log(3);
setTimeout(() => {
console.log(4);
}, 0);
console.log(5);
// 1 3 5 4 2
2. Promise (1)
Promise.resolve().then(() => {
console.log(1);
}).catch(() => {
console.log(2);
}).then(() => {
console.log(3);
})
// 1 3
3. Promise (2)
Promise.resolve().then(() => {
console.log(1);
throw new Error('erro1')
}).catch(() => {
console.log(2);
}).then(() => {
console.log(3);
})
// 1 2 3
4. Promise (3)
Promise.resolve().then(() => {
console.log(1);
throw new Error('erro1')
}).catch(() => {
console.log(2);
}).catch(() => {
console.log(3);
})
// 1 2
5. 宏任务和微任务
console.log(100)
setTimeout(() => {
console.log(200);
});
Promise.resolve().then(() => {
console.log(300);
})
console.log(400);
// 100 400 300 200
6. async/await (1)
(async function() {
console.log('start');
const a = await 100;
console.log('a:', a);
const b = await Promise.resolve(200);
console.log('b:', b);
const c = await Promise.reject(300);
console.log('c', c);
console.log('end');
})()
// start
// a: 100
// b: 200
// 报错
7. async/await (2)
async function async1 () {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2 () {
console.log('async2')
}
console.log('script start')
async1()
console.log('script end')
// script start
// async1 start
// async2
// script end
// async1 end
8.async/await (3)
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
await async3()
console.log('async1 end 2')
}
async function async2() {
console.log('async2')
}
async function async3() {
console.log('async3')
}
console.log('script start')
async1()
console.log('script end')
// script start
// async1 start
// async2
// script end
// async1 end
// async3
// async1 end 2
9. 综合题
async function async1 () {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1();
new Promise (function (resolve) {
console.log('promise1');
resolve()
}).then(function() {
console.log('promise2');
})
console.log('script end');
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
写在最后
如果你觉得我写的还不错,可以给我点个赞哦,如果有写错的地方,也欢迎大家评论指出,谢谢。