什么是迭代器?
JS 迭代器是一种遍历访问数据结构中所有成员的机制,本质是一个指针对象。
为什么要有迭代器?
- 为各种不同的数据结构提供统一的访问机制。
- 自定义数据结构的遍历:当你创建了一个自定义的数据结构时,可以实现迭代器来方便地遍历其中的元素。
- 惰性计算:迭代器可以实现惰性计算,即只有在需要时才计算下一个值,这样可以节省内存和计算资源。
- 异步迭代器,可以用于处理异步数据流。
迭代器的遍历过程
-
创建一个指针对象,指向数据结构的起始位置。
-
第一次调用指针对象的 next 方法,指针指向数据结构的第一个成员。
-
第二次调用指针对象的 next 方法,指针指向数据结构的第二个成员。
-
依此类推,不断调用指针对象的 next 方法……
-
当指针指向数据结构的结束位置,遍历结束
可迭代对象
实现了迭代器的对象(拥有返回一个具备 next 方法的 Symbol.iterator 方法),被称为可迭代对象
(如字符串、数组、Set、Map 、Object 等),可通过 for…of 语句遍历。
Symbol.iterator 是一个 Symbol 类型的值,它是 JavaScript 内置的一个特殊 Symbol,用来表示对象的迭代器方法。由于它是 Symbol 类型而非普通字符串,所以不能直接当作静态属性名来使用,而需要通过计算属性名的方式来定义或访问(即需要用 [] 包裹使用)。
可迭代对象调用其 Symbol.iterator 方法会得到一个用于遍历该对象元素的迭代器。
const arr = [1, 2];
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: undefined, done: true }
内置可迭代对象
JS 内置实现了迭代器的对象(具有 Symbol.iterator 属性)有:
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
自定义可迭代对象
// 创建一个可迭代对象
const myIteratorObject = {
data: [1, 2, 3, 4, 5],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return { value: this.data[index++], done: false };
} else {
return { done: true };
}
}
};
}
};
// 使用 for...of 循环遍历自定义的可迭代对象
for (const value of myIteratorObject) {
console.log(value);
}
触发迭代器的场景
-
for…of 循环
-
对数组和 Set 结构进行解构赋值
-
扩展运算符(…)
-
yield*
可迭代对象在 yield* 后时,会触发迭代器let generator = function* () { yield 1; yield* [2,3,4]; yield 5; }; var iterator = generator(); iterator.next() // { value: 1, done: false } iterator.next() // { value: 2, done: false } iterator.next() // { value: 3, done: false } iterator.next() // { value: 4, done: false } iterator.next() // { value: 5, done: false } iterator.next() // { value: undefined, done: true }
-
Array.from()
-
Map(), Set(), WeakMap(), WeakSet()(比如new Map([[‘a’,1],[‘b’,2]]))
-
Promise.all()
-
Promise.race()
可迭代对象转数组
只要某个数据结构实现了迭代器,就可以对它使用扩展运算符,将其转为数组。
let arr = [...iterable];
return()方法
可迭代对象除了具有next()方法,还可以具有return()方法,但可以选择性实现。
如果for…of循环提前退出(通常是因为出错,或者有break语句),就会调用return()方法。
如果一个对象在完成遍历前,需要清理或释放资源,就可以通过return()方法。
return()方法必须返回一个对象
function readLinesSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return { done: false };
},
return() {
file.close();
return { done: true };
}
};
},
};
}
下面的两种情况,都会触发执行return()方法。
// 情况一
for (let line of readLinesSync(fileName)) {
console.log(line);
break;
}
// 情况二
for (let line of readLinesSync(fileName)) {
console.log(line);
throw new Error();
}
同步迭代器
迭代器有 next 方法,返回一个包含 value 和 done 两个属性的对象。
- value 属性:当前迭代位置的值,可为任意类型( TS 中为 any) 。(即当前指针指向的数据结构的成员),当值为 undefined 时可省略。
- done 属性:是否已迭代结束,布尔值,true 表明迭代结束,false 则意味着还有成员可供迭代,当值为 false 时可省略。
// 自定义迭代器
const myIterator = {
// 需要遍历的成员
data:[1,2,3],
// 因第一个元素下标为 0 ,起始位置从 -1 开始
index: -1,
// 迭代器的核心方法 next
next() {
this.index++;
// next 方法返回一个包含 value 和 done 两个属性的对象
return {
value: this.data[this.index],
done: this.index === this.data.length };
},
// 为了后续可用 for of 语句遍历,详见下文中可迭代对象的解析
[Symbol.iterator]() {
return this;
},
};
for (const item of myIterator) {
console.log(item); //1 2 3
}
异步迭代器
异步迭代器和同步迭代器相似,主要用于处理异步数据。
其 next 方法返回一个 Promise,该 Promise 会解析为一个包含 value 和 done 属性的对象。
// 定义异步迭代器
const asyncNumberIterator = {
current: 0,
max: 3,
async next() {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 1000));
if (this.current <= this.max) {
const value = this.current++;
return { value, done: false };
}
return { done: true };
},
[Symbol.asyncIterator]() {
return this;
}
};
// 使用异步迭代器,需用 for await of
(async () => {
for await (const num of asyncNumberIterator) {
console.log(num);
}
})();
TS 给迭代器标注类型
// 迭代器接口
interface Iterable {
[Symbol.iterator]() : Iterator,
}
// 迭代器指针
interface Iterator {
next(value?: any) : IterationResult,
}
// next 的返回值
interface IterationResult {
value: any,
done: boolean,
}
使用生成器实现迭代器
用 yield 命令给出每一步的返回值即可。
let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};
for (let x of obj) {
console.log(x);
}
// "hello"
// "world"
【实战】迭代器实现“链表”结构
function Obj(value) {
this.value = value;
this.next = null;
}
Obj.prototype[Symbol.iterator] = function() {
var iterator = { next: next };
var current = this;
function next() {
if (current) {
var value = current.value;
current = current.next;
return { done: false, value: value };
}
return { done: true };
}
return iterator;
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for (var i of one){
console.log(i); // 1, 2, 3
}
【实战】类似数组的对象实现迭代器
存在数值键名和length属性的对象为类似数组的对象。
将其 Symbol.iterator方法直接引用数组的 Iterator 接口可快捷实现迭代器
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}