文章目录
前言
以下是几道高质量的 ES6+ 综合面试题,覆盖变量作用域、闭包、Promise、解构、this、async/await 等知识点,适合中高级前端岗位筛选与自我训练:
✅ 题目 1:输出顺序题(async + Promise + 微任务)
console.log("1");
async function asyncFunc() {
console.log("2");
await Promise.resolve();
console.log("3");
}
asyncFunc();
Promise.resolve().then(() => {
console.log("4");
});
console.log("5");
🧠 你能写出输出顺序吗?为什么?
✅ 正确输出:
1
2
5
4
3
这道题是ES6 面试经典题,考察你对以下内容的理解:
📌 涉及知识点:
async/await
的本质Promise
微任务(microtask)- JavaScript 的 事件循环(Event Loop)
- 同步 vs 微任务执行顺序
✅ 原始代码:
console.log("1");
async function asyncFunc() {
console.log("2");
await Promise.resolve();
console.log("3");
}
asyncFunc();
Promise.resolve().then(() => {
console.log("4");
});
console.log("5");
✅ 输出顺序是什么?
1
2
5
4
3
🧠 步骤详解:
🔷 第一步:执行同步代码
console.log("1")
输出 →1
声明
asyncFunc
(不会执行,跳过)调用
asyncFunc()
:- 输出
2
- 遇到
await Promise.resolve()
→ 进入暂停状态,并将后面的console.log("3")
注册为一个微任务
- 输出
注册一个
Promise.resolve().then(...)
→ 将console.log("4")
注册为另一个微任务console.log("5")
输出 →5
🔷 第二步:执行微任务队列(当前宏任务执行完后)
- 执行
Promise.resolve().then(...)
→ 输出4
await
后面的微任务恢复 → 输出3
🧠 async/await 背后发生了什么?
await
会把其后面的语句(如console.log("3")
)放入微任务队列,不会立刻执行!
等价于:
function asyncFunc() {
console.log("2");
return Promise.resolve().then(() => {
console.log("3");
});
}
✅ 最终执行顺序汇总
阶段 | 语句 | 输出 |
---|---|---|
同步 | console.log("1") |
1 |
同步 | asyncFunc() → log(“2”) |
2 |
同同步 | console.log("5") |
5 |
微任务队列 | .then(() => console.log("4")) |
4 |
微任务队列 | await 后 log(“3”) |
3 |
✅ 小结口诀(面试必备)
await
后的代码是微任务,必须等当前同步执行完 + 微任务队列依次执行;多个.then()
和await
的顺序取决于注册先后。
✅ 题目 2:作用域与解构陷阱题
let a = 1;
function test() {
console.log(a);
let a = 2;
}
test();
❓这段代码会输出什么?
✅ 正确答案:
报错:Cannot access 'a' before initialization
🧠 原因:
- 虽然是函数作用域,但
let a = 2
在作用域中形成暂时性死区(TDZ),在此之前访问会报错。
✅ 题目 3:this 指向 + 箭头函数
const obj = {
name: "vue",
say() {
setTimeout(function () {
console.log("1:", this.name)
}, 0)
setTimeout(() => {
console.log("2:", this.name)
}, 0)
}
}
obj.say();
✅ 输出:
1: undefined
2: vue
🧠 原因:
- 普通函数
function () {}
的this
是window
(非严格模式); - 箭头函数不绑定 this,继承自
obj
。
✅ 题目 4:数组解构与默认值
const [a = 1, b = a + 1, c = b + 1] = [undefined, undefined];
console.log(a, b, c);
✅ 输出:
1 2 3
🧠 解构中默认值是惰性求值,逐个执行:
- a = 1
- b = a + 1 = 2
- c = b + 1 = 3
✅ 题目 5:Promise 链式调用陷阱题
Promise.resolve()
.then(() => {
console.log('A');
return Promise.resolve('B');
})
.then((res) => {
console.log(res);
});
✅ 输出:
A
B
🧠 链式 then 中 return Promise,会等待该 Promise 解析完成并传递结果到下一个 then。
✅ 题目 6:typeof 与 Symbol
const sym = Symbol('test');
console.log(typeof sym);
console.log(typeof Symbol);
✅ 输出:
symbol
function
🧠 Symbol 是基本类型;但 Symbol
构造器本身是函数。
✅ 原始代码回顾(题目 3):
async function async1() {
console.log("1");
await async2();
console.log("2");
}
async function async2() {
console.log("3");
}
console.log("4");
setTimeout(() => {
console.log("5");
}, 0);
async1();
new Promise((resolve) => {
console.log("6");
resolve();
}).then(() => {
console.log("7");
});
console.log("8");
🧠 我们重点看 "2"
是怎么变成微任务的
🔹 1. 调用 async1()
打印
"1"
(同步执行)遇到
await async2()
:- 执行
async2()
,打印"3"
(同步) await
的行为:暂停函数的后续代码,挂起为微任务
- 执行
这意味着:
await async2();
console.log("2");
这两行中的第二行 console.log("2")
,会被推迟到微任务队列中,在当前宏任务完成后执行。
🔁 等价的 Promise 写法是什么?
function async1() {
console.log("1")
return async2().then(() => {
console.log("2")
})
}
✅ 那为什么 "7"
会比 "2"
先打印?
因为这行代码:
new Promise((resolve) => {
console.log("6")
resolve()
}).then(() => {
console.log("7")
});
中:
.then(() => { console.log("7") })
是在当前宏任务中同步创建并注册的微任务;- 而
console.log("2")
是async1 函数在await
之后才挂起注册的微任务。
所以注册顺序是:
微任务 1:then(() => console.log("7")) ← 更早注册
微任务 2:await async2() 后 console.log("2") ← 稍后注册
✅ 最终执行顺序解释:
阶段 | 内容 | 输出 |
---|---|---|
同步任务 | 4 → 1 → 3 → 6 → 8 | 4, 1, 3, 6, 8 |
微任务队列 | then → console.log(“7”) | 7 |
微任务队列 | await 后 console.log(“2”) | 2 |
宏任务 | setTimeout → console.log(“5”) | 5 |
🧠 小结:为什么 "2"
是微任务而 "7"
先执行?
await
会把await
后的语句包装成微任务,但它的注册是异步的,所以在当前宏任务后期才注册;
而.then()
是 同步注册微任务,因此先于await
后代码执行。