引言:当你的代码遇到"不可迭代"的尴尬
想象一下这个场景:你正在写一个优雅的函数,准备用 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) :字符界的循环高手,一个字符一个字符地来;
Map
和Set
:ES6 的新贵,也是循环的好手;- 类数组对象 :比如
arguments
、NodeList
,虽然不是数组,但也能循环; - 生成器函数:高级循环技巧,可以"暂停"和"继续"。
判断方法
方法一:最简单粗暴
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
- 优点:代码最简洁;
- 缺点:性能稍差,因为要实际执行迭代操作;
深入理解
迭代器是如何工作的?
迭代器就像一个"智能服务员",它知道:
- 现在轮到谁了(状态);
- 下一个是谁(
next
方法); - 还有没有下一个(
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
),因为它简单、快速、可靠。只有在需要更严格检查的特殊场景下,才考虑其他方法。