前端手撕代码(字节二面)

230 阅读3分钟

字节二面手撕代码

字节二面主要是以业务项目为主,最后考了两道简单的手撕代码,分别是闭包打印循环变量和Promise.all的实现。

第一道题:for 循环中的定时器回调函数打印循环变量 i

题目描述
在一个 for 循环中,使用定时器回调函数打印循环变量 i,分别用 varlet 声明 i,打印结果有什么不同?

知识点
这道题考察的是 JavaScript 中 varlet 的作用域差异,以及闭包的机制。

代码示例

// 使用 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

原因分析

  1. var 的作用域var 是函数作用域,循环中的 i 在整个函数范围内共享。当定时器回调执行时,循环已经结束,i 的值为 3,因此所有回调都打印 3
  2. 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);
        }
      );
    });
  });
}

关键点解析

  1. Promise.resolve:将输入值包装为 Promise,确保可以处理非 Promise 值。
  2. 结果存储:使用数组 results 存储每个 Promise 的结果,并通过索引确保顺序正确。
  3. 错误处理:一旦有 Promise 失败,立即调用 reject,终止整个流程。
  4. 完成条件:当所有 Promise 都成功时,调用 resolve 返回结果。

面试官可能追问

  • 如何处理输入数组为空的情况?
    回答:如果输入数组为空,直接调用 resolve 返回空数组。

    if (promises.length === 0) {
      resolve([]);
    }
    
  • 如何处理输入数组中包含非 Promise 值?
    回答:通过 Promise.resolve 将非 Promise 值转换为已解决的 Promise。


总结

二面两道题都不难,考察了 JavaScript 的核心概念:

  1. 作用域与闭包varlet 的区别,以及如何避免闭包陷阱。
  2. Promise 与异步编程:如何实现 Promise.all,并处理异步流程中的错误。