面试题:如何判断一个对象是否为可迭代对象?

引言:当你的代码遇到"不可迭代"的尴尬

想象一下这个场景:你正在写一个优雅的函数,准备用 for...of 循环来遍历传入的参数,结果浏览器无情地抛出了一个错误:

javascript 复制代码
Uncaught TypeError: obj is not iterable

😱 那一刻,你的内心是崩溃的。

什么是可迭代对象? 简单来说,就是那些可以被"遍历"的对象,比如数组、字符串、Map、Set 等。它们就像是"有顺序的菜",你可以一道一道地品尝。

为什么需要判断? 因为不是所有对象都这么"听话"。有些对象就像熊孩子一样,你让它排队它偏不排,这时候就需要先识别出对象是不是"熊孩子"。

可迭代对象

迭代器协议:JavaScript 的"循环规则"

在 JavaScript 的世界里,有一套严格的"循环规则",叫做迭代器协议(Iterator Protocol)

这个协议的核心就是 Symbol.iterator 方法。如果一个对象有这个"循环许可证",那它就是可迭代的。

javascript 复制代码
// 就像每个人都有身份证一样,可迭代对象都有这个"循环许可证"
console.log([1, 2, 3][Symbol.iterator]); // ƒ values() { [native code] }
console.log("hello"[Symbol.iterator]);   // ƒ [Symbol.iterator]() { [native code] }
console.log({a: 1}[Symbol.iterator]);    // undefined - 这个对象没有"循环许可证"!

常见的可迭代对象

让我们认识一下 JavaScript 世界里的可迭代对象们:

  • 数组(Array) :最守规矩的循环达人,永远按顺序来;
  • 字符串(String) :字符界的循环高手,一个字符一个字符地来;
  • MapSet:ES6 的新贵,也是循环的好手;
  • 类数组对象 :比如 argumentsNodeList,虽然不是数组,但也能循环;
  • 生成器函数:高级循环技巧,可以"暂停"和"继续"。

判断方法

方法一:最简单粗暴

javascript 复制代码
function isIterable(obj) {
  return obj != null && typeof obj[Symbol.iterator] === 'function';
}

console.log(isIterable([1, 2, 3]));     // true
console.log(isIterable("hello"));       // true
console.log(isIterable({a: 1}));        // false
console.log(isIterable(null));          // false
  • 优点:简单直接;
  • 缺点:有时候"可迭代对象"可能是假的(比如返回的不是真正的迭代器);

方法二:更严格

javascript 复制代码
function isIterable(obj) {
  try {
    const iterator = obj[Symbol.iterator]();
    return typeof iterator.next === 'function';
  } catch (e) {
    return false;
  }
}

console.log(isIterable([1, 2, 3]));     // true
console.log(isIterable("hello"));       // true
console.log(isIterable({a: 1}));        // false
  • 优点:更严格;
  • 缺点:稍微慢一点;

方法三:最魔幻

javascript 复制代码
function isIterable(obj) {
  try {
    [...obj];  // 展开运算符,就像魔法一样
    return true;
  } catch (e) {
    return false;
  }
}

console.log(isIterable([1, 2, 3]));     // true
console.log(isIterable("hello"));       // true
console.log(isIterable({a: 1}));        // false
  • 优点:代码最简洁;
  • 缺点:性能稍差,因为要实际执行迭代操作;

深入理解

迭代器是如何工作的?

迭代器就像一个"智能服务员",它知道:

  1. 现在轮到谁了(状态);
  2. 下一个是谁(next 方法);
  3. 还有没有下一个(done 属性)。
javascript 复制代码
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

就像服务员一个一个地端菜,端完了就说"没有了"。

自定义可迭代对象

javascript 复制代码
class MyIterable {
  constructor(data) {
    this.data = data;
  }
  
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index < this.data.length) {
          return { value: this.data[index++], done: false };
        }
        return { done: true };
      }
    };
  }
}

const myIterable = new MyIterable(['apple', 'banana', 'orange']);
for (const item of myIterable) {
  console.log(item);
}

性能对比

让我们来做个性能测试,看看哪种方法最快:

javascript 复制代码
function isIterable1(obj) {
  return obj != null && typeof obj[Symbol.iterator] === 'function';
}

function isIterable2(obj) {
  try {
    const iterator = obj[Symbol.iterator]();
    return typeof iterator.next === 'function';
  } catch (e) {
    return false;
  }
}

function isIterable3(obj) {
  try {
    [...obj];  // 展开运算符,就像魔法一样
    return true;
  } catch (e) {
    return false;
  }
}

// 性能测试
const testData = Array.from({length: 10000}, (_, i) => i);

console.time('方法一');
for (let i = 0; i < 10000; i++) {
  isIterable1(testData);
}
console.timeEnd('方法一');

console.time('方法二');
for (let i = 0; i < 10000; i++) {
  isIterable2(testData);
}
console.timeEnd('方法二');

console.time('方法三');
for (let i = 0; i < 10000; i++) {
  isIterable3(testData);
}
console.timeEnd('方法三');

运行结果:

可以看出,方法一 方法二 > 方法三。

最后

在实际开发中,推荐使用方法一(检查 Symbol.iterator),因为它简单、快速、可靠。只有在需要更严格检查的特殊场景下,才考虑其他方法。

相关推荐
张志鹏PHP全栈3 分钟前
postcss-px-to-viewport如何实现单页面使用?
前端
恋猫de小郭3 分钟前
iOS 26 正式版即将发布,Flutter 完成全新 devicectl + lldb 的 Debug JIT 运行支持
android·前端·flutter
前端进阶者33 分钟前
electron-vite_20外部依赖包上线后如何更新
前端·javascript·electron
uhakadotcom1 小时前
如何安装和使用开源的Meilisearch
后端·面试·github
晴空雨1 小时前
💥 React 容器组件深度解析:从 Props 拦截到事件改写
前端·react.js·设计模式
Marshall35721 小时前
前端水印防篡改原理及实现
前端
在未来等你1 小时前
RabbitMQ面试精讲 Day 27:常见故障排查与分析
中间件·面试·消息队列·rabbitmq
阿虎儿1 小时前
TypeScript 内置工具类型完全指南
前端·javascript·typescript
IT_陈寒1 小时前
Java性能优化实战:5个立竿见影的技巧让你的应用提速50%
前端·人工智能·后端