字节二面手撕代码
字节二面主要是以业务项目为主,最后考了两道简单的手撕代码,分别是闭包打印循环变量和Promise.all的实现。
第一道题:for 循环中的定时器回调函数打印循环变量 i
题目描述
在一个 for
循环中,使用定时器回调函数打印循环变量 i
,分别用 var
和 let
声明 i
,打印结果有什么不同?
知识点
这道题考察的是 JavaScript 中 var
和 let
的作用域差异,以及闭包的机制。
代码示例
// 使用 var 声明 i
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
// 使用 let 声明 i
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
运行结果
- 使用
var
声明时,输出结果为:3, 3, 3
。 - 使用
let
声明时,输出结果为:0, 1, 2
。
原因分析
var
的作用域:var
是函数作用域,循环中的i
在整个函数范围内共享。当定时器回调执行时,循环已经结束,i
的值为3
,因此所有回调都打印3
。let
的作用域:let
是块级作用域,在每次循环中都会创建一个新的i
变量。定时器回调捕获的是每次循环中独立的i
值,因此输出结果为0, 1, 2
。
面试官可能追问
- 如果用
var
,如何让结果变为0, 1, 2
?
回答:可以通过立即执行函数(IIFE)创建闭包,将当前循环的i
值传递给闭包,避免变量被覆盖。for (var i = 0; i < 3; i++) { (function(j) { setTimeout(() => { console.log(j); }, 0); })(i); }
第二道题:实现一个 Promise.all 方法
题目描述
实现一个 Promise.all
方法,接收一个 Promise 数组,返回一个新的 Promise。当所有输入的 Promise 都成功时,返回的 Promise 成功;如果有一个失败,则返回的 Promise 失败。
知识点
这道题考察对 Promise 的理解和异步编程能力。
代码实现
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completedCount = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
(value) => {
results[index] = value;
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
},
(reason) => {
reject(reason);
}
);
});
});
}
关键点解析
- Promise.resolve:将输入值包装为 Promise,确保可以处理非 Promise 值。
- 结果存储:使用数组
results
存储每个 Promise 的结果,并通过索引确保顺序正确。 - 错误处理:一旦有 Promise 失败,立即调用
reject
,终止整个流程。 - 完成条件:当所有 Promise 都成功时,调用
resolve
返回结果。
面试官可能追问
-
如何处理输入数组为空的情况?
回答:如果输入数组为空,直接调用resolve
返回空数组。if (promises.length === 0) { resolve([]); }
-
如何处理输入数组中包含非 Promise 值?
回答:通过Promise.resolve
将非 Promise 值转换为已解决的 Promise。
总结
二面两道题都不难,考察了 JavaScript 的核心概念:
- 作用域与闭包:
var
和let
的区别,以及如何避免闭包陷阱。 - Promise 与异步编程:如何实现
Promise.all
,并处理异步流程中的错误。