飞书面试
题目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
函数内部,闭包能够访问它。