关于 forEach 常见面试题

198 阅读2分钟

forEach 在编码中是比较常用方法,面试中也会常遇见。本文总结关于 forEach 常见面试题:

  • 如何跳出 forEach
  • forEach 与 async/await

如何跳出 forEach

function foo(arr) {
  arr.forEach((item, index) => {
    if (index === 1) {
      // 👉️ 跳出 forEach
    }
    console.log(item);
  });
}
foo([1, 2, 3]);

大部分初学者,第一反应会想到 break 语句。

singsong: 我曾经也这么干过 🤣

  • break

    function foo(arr) {
      arr.forEach((item, index) => {
        if (index === 1) {
          break;
        }
        console.log(item);
      });
    }
    foo([1, 2, 3]);
    

    可运行,报 "Uncaught SyntaxError: Illegal break statement" 错误 ❌;原因是我们尝试在函数中使用 break 语句。而 break 只能适用如下场景:

    • for 循环
    • for ... of
    • for ... in
    • while 循环
    • switch 语句

    竟然是函数,那是不是可使用 return ?🤔

  • return

    function foo(arr) {
      arr.forEach((item, index) => {
        if (index === 1) {
          return;
        }
        console.log(item);
      });
    }
    foo([1, 2, 3]);
    
    // 输出结果:
    // 1
    // 3
    

    这里预期结果 "1",而输出结果 "1 3",与预期不符。换个思路,使用 try/catch

    • try/catch
    function foo(arr) {
      try {
        arr.forEach((item, index) => {
          if (index === 1) {
            throw new Error();
          }
          console.log(item);
        });
      } catch (error) {}
    }
    foo([1, 2, 3]);
    
    // 输出结果:
    // 1
    

    输出符合预期 😆

forEachasync/await

const foo = (n) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(n);
    }, 1000);
  });
};

const test = (nums) => {
  nums.forEach(async (n) => {
    let num = await foo(n);
    console.log(num);
  });
};

test([1, 2, 3]);

// 输出结果
// 1
// 2
// 3

执行结果:“1 秒后,一次性输出 1,2,3”,即并行执行;而期望输出:“每间隔 1 秒,依次输出 1,2,3”,即串行执行。啥原因?🤔

首先,我们先来看看 forEach 内部是如何实现的。这里参考 MDN forEach 的 polyfill 实现:Array.prototype.forEach()。简化版本:

Array.prototype.forEach = function (callback) {
  for (let index = 0; index < this.length; index++) {
    callback(this[index], index, this);
  }
};

上述实例,等同于

const foo = (n) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(n);
    }, 1000);
  });
};

const test = (nums) => {
  // nums.forEach(async n=>{
  //     let num = await foo(n);
  //     console.log(num);
  // });
  for (let index = 0; index < nums.length; index++) {
     // 同步执行
    (async (n) => {
      let num = await foo(n);
      console.log(num);
    })(nums[index], index, nums);
  }
};

test([1, 2, 3]);

因此,了解 forEach 并行执行的原因。回到面试问题,如何将 并行执行 变为 串行执行

  • 改造 forEachasyncForEach

    async function asyncForEach(array, callback) {
      for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array);
      }
    }
    

    使用 asyncForEach 替换 forEach

    async function asyncForEach(array, callback) {
      for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array);
      }
    }
    
    const foo = (num) => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(num);
        }, 1000);
      });
    };
    
    const test = (nums) => {
      // nums.forEach(async n=>{
      //     let num = await foo(n);
      //     console.log(num);
      // });
      asyncForEach(nums, async (n) => {
        let num = await foo(n);
        console.log(num);
      });
    };
    
    test([1, 2, 3]);
    
  • 使用 for...of 替换 forEach

    const foo = (num) => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(num);
        }, 1000);
      });
    };
    
    const test = async (nums) => {
      for (let n of nums) {
        let num = await foo(n);
        console.log(num);
      }
    };
    
    test([1, 2, 3]);
    

总结

本文介绍的两个面试题,主要围绕 forEach 内部实现来出题的。因此只要了解 forEach 内部实现细节,再结合其他 JS 知识应该能很快地想到解决方法。