飞书面试
题目1:
async function foo() {console.log('foo start');await bar();console.log('foo end');
}async function bar() {console.log('bar start');return new Promise((resolve, reject) => {setTimeout(() => {console.log('bar promise');resolve();}, 1000);});
}console.log('script start');
foo();
setTimeout(() => {console.log('settimeout');
}, 0);
分析:
首先打印script start。
然后调用foo函数,在foo函数内部先打印foo start,接着等待bar函数。
在bar函数内部,先打印bar start,然后返回一个 1 秒后resolve的Promise。
1 秒后,Promise解决,打印bar promise,然后回到foo函数,打印foo end。
最后执行setTimeout中的函数,打印settimeout。
script start
foo start
bar start
bar promise
foo end
settimeout
以下是几道包含 await、async 以及 new 立即执行相关内容,考查执行顺序的题目及详细解析:
题目 1:
async function asyncFunc1() {console.log('asyncFunc1 start');const result = await new Promise((resolve) => {console.log('Inside new Promise of asyncFunc1');resolve(10);});console.log('asyncFunc1 got result:', result);console.log('asyncFunc1 end');
}async function asyncFunc2() {console.log('asyncFunc2 start');const innerPromise = new Promise((resolve) => {console.log('Inside new Promise of asyncFunc2');setTimeout(() => {resolve(20);console.log('Timeout resolved in asyncFunc2');}, 500);});const result = await innerPromise;console.log('asyncFunc2 got result:', result);console.log('asyncFunc2 end');
}console.log('Script start');
asyncFunc1();
asyncFunc2();
console.log('Script end');
解析:
-
整体执行开始:
首先会执行同步代码,遇到console.log('Script start');,会直接打印出Script start。 -
调用
asyncFunc1函数:- 进入
asyncFunc1函数后,先打印asyncFunc1 start,这是函数内的第一个同步代码执行的打印。 - 接着遇到
await关键字以及后面跟着的new Promise。new Promise的构造函数是同步执行的,所以会立即打印Inside new Promise of asyncFunc1,然后这个Promise被resolve,但是由于await的存在,函数在此处暂停执行,等待Promise完成(其实这里已经完成了,因为是立即resolve的),把执行权交回主程序。
- 进入
-
调用
asyncFunc2函数:- 进入
asyncFunc2函数后,先打印asyncFunc2 start,接着执行new Promise的构造函数,这是同步操作,会打印Inside new Promise of asyncFunc2。不过这个Promise内部设置了一个setTimeout,setTimeout是异步的,会在 500 毫秒后执行resolve,所以当前asyncFunc2函数执行到await处也暂停了,把执行权交回主程序。
- 进入
-
继续主程序的同步代码:
会执行到console.log('Script end');,打印出Script end。 -
异步操作完成后继续执行:
- 此时主程序同步代码执行完毕,开始处理异步队列中的任务。
- 对于
asyncFunc1,之前await的Promise已经resolve,继续执行后面的代码,打印asyncFunc1 got result: 10和asyncFunc1 end。 - 对于
asyncFunc2,500 毫秒后setTimeout里的resolve执行,然后继续执行后面代码,打印Timeout resolved in asyncFunc2、asyncFunc2 got result: 20和asyncFunc2 end。
预期输出顺序:
Script start
asyncFunc1 start
Inside new Promise of asyncFunc1
asyncFunc2 start
Inside new Promise of asyncFunc2
Script end
asyncFunc1 got result: 10
asyncFunc1 end
Timeout resolved in asyncFunc2
asyncFunc2 got result: 20
asyncFunc2 end
题目 2:
async function firstAsync() {console.log('First async start');const result1 = await new Promise((resolve) => {console.log('Promise in firstAsync created');setTimeout(() => {resolve(5);console.log('Timeout resolved in firstAsync');}, 300);});console.log('First async got result:', result1);console.log('First async end');
}async function secondAsync() {console.log('Second async start');const result2 = await new Promise((resolve) => {console.log('Promise in secondAsync created');resolve(10);});console.log('Second async got result:', result2);console.log('Second async end');
}console.log('Main script start');
firstAsync();
secondAsync();
console.log('Main script end');
解析:
-
开始执行:
先执行同步代码,遇到console.log('Main script start');,打印Main script start。 -
调用
firstAsync函数:- 进入
firstAsync函数,打印First async start,接着遇到await及后面的new Promise。new Promise的构造函数同步执行,打印Promise in firstAsync created,但里面的setTimeout是异步的,300 毫秒后才会resolve,所以函数执行到await处暂停,执行权交回主程序。
- 进入
-
调用
secondAsync函数:- 进入
secondAsync函数,打印Second async start,然后执行new Promise的构造函数,打印Promise in secondAsync created,此Promise立即resolve,不过由于await,会暂停在此处等待Promise完成(实际已经完成),执行权交回主程序。
- 进入
-
继续主程序同步代码:
执行console.log('Main script end');,打印Main script end。 -
异步操作完成后继续执行:
- 对于
secondAsync,之前await的Promise已完成,继续执行后面代码,打印Second async got result: 10和Second async end。 - 300 毫秒后,
firstAsync中setTimeout的resolve执行,然后继续执行后面代码,打印Timeout resolved in firstAsync、First async got result: 5和First async end。
- 对于
预期输出顺序:
Main script start
First async start
Promise in firstAsync created
Second async start
Promise in secondAsync created
Main script end
Second async got result: 10
Second async end
Timeout resolved in firstAsync
First async got result: 5
First async end
题目 3:
async function outerAsync() {console.log('Outer async start');const innerPromise = new Promise((resolve) => {console.log('Inner promise created');const innerAsync = async () => {console.log('Inner async start');const result = await new Promise((innerResolve) => {console.log('Inner inner promise created');innerResolve(15);});console.log('Inner async got result:', result);console.log('Inner async end');resolve(result);};innerAsync();});const outerResult = await innerPromise;console.log('Outer async got result:', outerResult);console.log('Outer async end');
}console.log('Script begins');
outerAsync();
console.log('Script continues');
解析:
-
开始执行:
执行同步代码,打印Script begins。 -
调用
outerAsync函数:- 进入
outerAsync函数,打印Outer async start。 - 接着执行
new Promise的构造函数,打印Inner promise created。 - 然后定义并调用
innerAsync函数,进入innerAsync函数后,打印Inner async start,再遇到await及后面的new Promise,这个new Promise的构造函数同步执行,打印Inner inner promise created,然后立即resolve,由于await,暂停在这等待Promise完成(实际已完成),执行权交回outerAsync函数中new Promise的代码处(也就是innerAsync函数外面那层Promise)。
- 进入
-
继续
outerAsync函数:
此时innerAsync函数里的await暂停了,但outerAsync函数里new Promise中定义的innerAsync函数已经执行了,接着执行resolve(result)(result就是innerAsync里await得到的值15),然后由于outerAsync函数里也有await等待这个new Promise,会暂停在这,执行权交回主程序。 -
继续主程序同步代码:
执行console.log('Script continues');,打印Script continues。 -
异步操作完成后继续执行:
- 此时主程序同步代码执行完了,开始处理异步队列。
outerAsync函数里await的new Promise已经resolve,继续执行后面代码,打印Outer async got result: 15和Outer async end。
- 此时主程序同步代码执行完了,开始处理异步队列。
预期输出顺序:
Script begins
Outer async start
Inner promise created
Inner async start
Inner inner promise created
Script continues
Inner async got result: 15
Inner async end
Outer async got result: 15
Outer async end
这些题目涵盖了 async、await 与 new Promise(包括立即执行 resolve 和带异步操作如 setTimeout 后执行 resolve 的情况)之间的交互以及执行顺序的考查,希望能帮助你加深对这部分知识的理解和掌握。
当然可以,以下是原始代码和将 var 改为 let 后的代码的对比,以及它们的输出结果和解释。
var提升/闭包
var result = [];
var a = 3;
var total = 0;function foo(a) {var i = 0;for (; i < 3; i++) {result[i] = function() {total += i * a;console.log(total);};}
}foo(1);
result[0]();
result[1]();
result[2]();
输出结果:
3
6
9
解释:
var声明的i是函数作用域的,所以在循环结束后,i的值为 3。- 每个闭包都捕获了同一个
i变量,因此当这些闭包被调用时,它们都使用i的最终值 3。 a的值在foo函数调用时被设置为 1,并且由于var声明的变量是函数作用域的,所以所有闭包都使用这个值。
修改后的代码(使用 let,仅修改 i)
var result = [];
var a = 3;
var total = 0;function foo(a) {for (let i = 0; i < 3; i++) {result[i] = function() {total += i * a;console.log(total);};}
}foo(1);
result[0]();
result[1]();
result[2]();
输出结果:
0
1
3
解释:
let声明的i是块作用域的,每个循环迭代都创建了一个新的i变量。- 每个闭包捕获了其对应迭代的
i值,因此当这些闭包被调用时,它们分别使用 0、1 和 2。 a的值在foo函数调用时被设置为 1,并且由于var声明的变量是函数作用域的,所以所有闭包都使用这个值。
全部使用 let 的代码
let result = [];
let a = 3;
let total = 0;function foo(a) {for (let i = 0; i < 3; i++) {result[i] = function() {total += i * a;console.log(total);};}
}foo(1);
result[0]();
result[1]();
result[2]();
输出结果:
0
1
3
解释:
- 与上一个修改后的代码相同,因为
let声明的i是块作用域的,每个闭包捕获了其对应迭代的i值。 a和total也是块作用域的,但由于它们在全局作用域中声明,它们的行为与之前使用var时相同。
全部使用 let,包括 foo 函数内部的 a
let result = [];
let a = 3;
let total = 0;function foo() {let a = 1; // 这个 'a' 只在 foo 函数内部可见for (let i = 0; i < 3; i++) {result[i] = function() {total += i * a; // 这里使用的是函数内部的 'a'console.log(total);};}
}foo();
result[0]();
result[1]();
result[2]();
输出结果:
0
1
3
解释:
foo函数内部的a被设置为 1,并且由于let声明的变量是块作用域的,所以所有闭包都使用这个值。- 每个闭包捕获了其对应迭代的
i值,因此当这些闭包被调用时,它们分别使用 0、1 和 2。
当然,这里是每个练习题的答案和解释:
补充些类似的题目:
题目 1
for (var i = 0; i < 3; i++) {setTimeout(function() {console.log(i);}, 100 * i);
}
答案: 这段代码会输出 3 3 3。
解释: 在这个例子中,var 声明的变量 i 是函数作用域的。当 setTimeout 函数被调用时,它们会将当前的 i 值(在循环结束后为 3)捕获到闭包中。由于所有的 setTimeout 回调都共享同一个 i 变量,它们都打印出循环结束后的 i 值,即 3。
题目 2
for (let i = 0; i < 3; i++) {setTimeout(function() {console.log(i);}, 100 * i);
}
答案: 这段代码会输出 0 1 2。
解释: 使用 let 声明的变量 i 是块作用域的,这意味着每次循环迭代都会创建一个新的 i 变量。因此,每个 setTimeout 回调都捕获了其对应迭代的 i 值。当这些回调被执行时,它们打印出各自捕获的 i 值,即 0、1 和 2。
题目 3
function createFunctions() {var funcs = [];for (var i = 0; i < 3; i++) {funcs[i] = function() {return i * i;};}return funcs;
}var funcs = createFunctions();
console.log(funcs[0]()); // ?
console.log(funcs[1]()); // ?
console.log(funcs[2]()); // ?
答案: 这段代码会输出 9 9 9。
解释: 与题目 1 类似,var 声明的变量 i 是函数作用域的。当这些函数被创建时,它们都捕获了同一个 i 变量。当这些函数被调用时,循环已经结束,i 的值为 3。因此,每个函数都返回 3 * 3,即 9。
题目 4
function createFunctions() {var funcs = [];for (let i = 0; i < 3; i++) {funcs[i] = function() {return i * i;};}return funcs;
}var funcs = createFunctions();
console.log(funcs[0]()); // ?
console.log(funcs[1]()); // ?
console.log(funcs[2]()); // ?
答案: 这段代码会输出 0 1 4。
解释: 使用 let 声明的变量 i 是块作用域的,每个循环迭代都创建了一个新的 i 变量。每个闭包都捕获了其对应迭代的 i 值。因此,第一个函数返回 0 * 0,即 0;第二个函数返回 1 * 1,即 1;第三个函数返回 2 * 2,即 4。
题目 5
function getClosure() {var a = 10;function closure() {return a;}return closure;
}var myClosure = getClosure();
console.log(myClosure()); // ?
答案: 这段代码会输出 10。
解释: 在 getClosure 函数中,var 声明的变量 a 是函数作用域的。闭包 closure 捕获了 a 的值。当 myClosure 被调用时,它返回 a 的值,即 10。
题目 6
function getClosureWithLet() {let b = 20;function closure() {return b;}return closure;
}var myClosure = getClosureWithLet();
console.log(myClosure()); // ?
答案: 这段代码会输出 20。
解释: 在 getClosureWithLet 函数中,let 声明的变量 b 是块作用域的。闭包 closure 捕获了 b 的值。当 myClosure 被调用时,它返回 b 的值,即 20。这与使用 var 的情况类似,因为 b 的作用域是 getClosureWithLet 函数内部,闭包能够访问它。