JavaScript 为对象添加自定义的迭代器语法
梗概
JavaScript中,可以通过实现Symbol.iterator方法来为任何对象添加自定义的迭代规则,使其成为可迭代对象,从而能够使用for...of循环、展开运算符和解构赋值等特性。
基本语法
const myObject = {
// 对象的其他属性和方法
...
// 添加Symbol.iterator方法
[Symbol.iterator]() {
// 返回一个迭代器对象
return {
// 迭代器必须具有next方法
next() {
// next方法必须返回包含value和done属性的对象
return { value: 某个值, done: 布尔值 };
}
};
}
};迭代器协议
一个标准的迭代器必须符合以下要求:
- 迭代器必须是一个对象
- 迭代器对象必须有一个
next()方法 next()方法必须返回一个对象,包含两个属性:value: 当前迭代值done: 布尔值,表示迭代是否结束
详细示例
示例1:为普通对象添加迭代器
const person = {
name: 'John',
age: 30,
skills: ['JavaScript', 'React', 'Node.js'],
[Symbol.iterator]() {
// 要迭代的属性数组
const properties = Object.keys(this);
let index = 0;
// 返回迭代器对象
return {
next: () => {
if (index < properties.length) {
const key = properties[index++];
// 跳过Symbol.iterator方法
if (key === 'Symbol(Symbol.iterator)') {
return this.next();
}
return {
value: [key, this[key]],
done: false
};
} else {
return { done: true };
}
}
};
}
};
// 使用for...of循环遍历
for (const [key, value] of person) {
console.log(`${key}: ${value}`);
}
// 输出:
// name: John
// age: 30
// skills: JavaScript,React,Node.js示例2:创建自定义范围迭代器
function range(start, end, step = 1) {
return {
// 让函数返回的对象具有迭代器
[Symbol.iterator]() {
let current = start;
return {
next() {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
} else {
return { done: true };
}
}
};
}
};
}
// 使用自定义范围迭代器
for (const num of range(1, 10, 2)) {
console.log(num); // 1, 3, 5, 7, 9
}
// 结合展开运算符
const numbers = [...range(1, 5)]; // [1, 2, 3, 4, 5]示例3:为类添加迭代器
class Collection {
constructor(items = []) {
this.items = items;
}
add(item) {
this.items.push(item);
}
// 添加迭代器方法
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.items.length) {
return { value: this.items[index++], done: false };
} else {
return { done: true };
}
}
};
}
}
const collection = new Collection(['a', 'b', 'c']);
collection.add('d');
// 使用for...of遍历
for (const item of collection) {
console.log(item); // 'a', 'b', 'c', 'd'
}
// 结合解构赋值
const [first, ...rest] = collection;
console.log(first); // 'a'
console.log(rest); // ['b', 'c', 'd']可重用的迭代器
通常,迭代器是不可重用的,一旦迭代完成就不能再次迭代。如果需要可重用的迭代器,可以使迭代器自身也实现可迭代协议:
const repeatable = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
// 迭代器自身也实现Symbol.iterator方法
return {
// next方法
next() {
if (index < repeatable.data.length) {
return { value: repeatable.data[index++], done: false };
} else {
return { done: true };
}
},
// 迭代器本身也是可迭代的
[Symbol.iterator]() {
return this;
}
};
}
};
const iterator = repeatable[Symbol.iterator]();
// 第一次迭代
for (const item of iterator) {
console.log(item); // 1, 2, 3
}
// 重置索引
index = 0;
// 第二次迭代(不可能,因为迭代器已经用完了)
for (const item of iterator) {
console.log(item); // 不会输出任何内容
}异步迭代器
ES2018引入了异步迭代器,使用Symbol.asyncIterator而不是Symbol.iterator,并结合for await...of循环:
const asyncIterable = {
data: [1, 2, 3],
[Symbol.asyncIterator]() {
let index = 0;
return {
next() {
return new Promise((resolve) => {
setTimeout(() => {
if (index < asyncIterable.data.length) {
resolve({ value: asyncIterable.data[index++], done: false });
} else {
resolve({ done: true });
}
}, 1000);
});
}
};
}
};
// 使用异步迭代器
async function iterate() {
for await (const item of asyncIterable) {
console.log(item); // 每隔1秒依次输出: 1, 2, 3
}
}
iterate();注意事项
- 迭代器通常是一次性使用的,迭代完成后就不能再次使用,除非特意设计为可重用迭代器
- 迭代器的
next()方法可以接受参数,用于向迭代器传递值(例如在Generator中) - 在实现迭代器时,要注意闭包和
this绑定问题,建议使用箭头函数保持正确的this引用