📝 JavaScript 迭代器的强大功能和多功能性
2025-01-22 08:19:30    1.3k 字   
This post is also available in English and alternative languages.

Iterate Like a Pro: Mastering JavaScript Iterators for Effortless Code


在 Javascript 中内置了许多实现迭代器模式的结构,例如:Array、Set 和 Map 。在 Javascript 中,一个对象要成为可迭代的,必须实现 Iterable 接口。

但什么是 Iterable 接口呢?首先,要成为可迭代的,一个对象必须有一个 next 方法。这个方法必须返回两个属性:done 和 value 。 done 用于检测迭代是否完成;而 value 包含当前值。但同样重要的是,如果你想让你的对象成为一个迭代器,你必须在对象的 Symbol.iterator 中暴露可迭代接口,就像这个例子中一样。

1
2
3
4
5
const array = [1, 2, 3, 4, 5];
const iterator = array[Symbol.iterator]();
for (let result = iterator.next(); !result.done; result = iterator.next()) {
console.log(result.value);
}

例如,这里有一个作为迭代器实现的 range 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// ts 写法
const range = (start: number, end: number): Iterable<number> => {
return {
[Symbol.iterator]() {
let n = start;
return {
next() {
console.log("range next");
if (n > end) {
return { done: true, value: null };
}
return { done: false, value: n++ };
},
};
},
};
};


// 转换成 javascript
const range = (start, end) => {
return {
[Symbol.iterator]() {
let n = start;
return {
next() {
console.log("range next");
if (n > end) {
return {done: true, value: null};
}
return {done: false, value: n++};
},
};
},
};
};

如你所注意到的,这个函数接受两个数字, start 和 end ,并返回一个新的对象,该对象只有一个属性,在这种情况下是迭代器属性。

然后,在这个函数内部,有一个 next 函数,它在每次调用时检查当前值是否大于 end ,如果是,则返回一个 done 为 true 且 value 为 null 的新对象;否则返回一个 done 为 false 且 value 为当前值的对象。

迭代器的美妙之处在于,只有当你请求下一个值时, javascript 才会执行操作。

每个迭代器都可以使用 for-of 循环进行迭代:

1
2
3
for (const num of range(1, 10)) {
console.log(num);
}

或者使用它的原生方法,即调用 Symbol.iterator 函数,然后使用 next 方法并检查 done 属性是否为 true 。

1
2
3
4
const rangeIterator = range(1, 10)[Symbol.iterator]();
for (let result = rangeIterator.next(); !result.done; result = rangeIterator.next()) {
console.log(result.value);
}

也可以使用展开运算符将所有迭代器值复制到一个数组中。

1
2
3
for (const num of [...range(1, 10)]) {
console.log(num);
}

迭代器还有另一个方法,即 return 方法。这个方法用于代码没有完成迭代的情况。想象一下循环调用了 break 或 return ;在这种情况下, JavaScript 在底层为我们调用 return 方法。在这个方法中,我们可以处理我们需要的任何事情。我们可能需要重置某些内容或检查迭代器的当前值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// ts 写法
const range = (start: number, end: number): Iterable<number> => {
return {
[Symbol.iterator]() {
let n = start;
return {
next() {
console.log("range next");
if (n > end) {
return { done: true, value: null };
}
return { done: false, value: n++ };
},
return() {
console.log("range return");
return { done: true, value: null };
},
};
},
};
};

for (const num of range(1, 10)) {
if (num > 5) break;
console.log(num);
}


// 转换成 javascript
const range = (start, end) => {
return {
[Symbol.iterator]() {
let n = start;
return {
next() {
console.log("range next");
if (n > end) {
return { done: true, value: null };
}
return { done: false, value: n++ };
},
return() {
console.log("range return");
return { done: true, value: null };
}
};
}
};
};

for (const num of range(1, 10)) {
if (num > 5) break;
console.log(num);
}

迭代器是强大的,我们还可以创建接受迭代器并操作它以返回另一个迭代器的函数。例如,我们可以创建一个 map 函数,它接受一个迭代器并返回另一个迭代器,其中包含用户指定的回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// ts
function mapIterable<T, U>(
iterable: Iterable<T>,
callback: (value: T) => U
): Iterable<U> {
return {
[Symbol.iterator]() {
const iterator = iterable[Symbol.iterator]();
return {
next() {
console.log("mapIterable next");
const { done, value } = iterator.next();
if (done) {
return { done: true, value: null };
}
return { done, value: callback(value) };
},
return() {
console.log("mapIterable return");
if (iterator.return) {
iterator.return();
}
return { done: true, value: null };
},
};
},
};
}


// 转换成 javascript
function mapIterable(iterable, callback) {
return {
[Symbol.iterator]() {
const iterator = iterable[Symbol.iterator]();
return {
next() {
console.log("mapIterable next");
const { done, value } = iterator.next();
if (done) {
return { done: true, value: null };
}
return { done, value: callback(value) };
},
return() {
console.log("mapIterable return");
if (iterator.return) {
iterator.return();
}
return { done: true, value: null };
},
};
},
};
}

前面所说的所有信息对这个新迭代器也是正确的。 JavaScript 在代码库不请求下一个值之前不会做任何事情;对于 return 方法也是如此,现在你可以将 range 迭代器与 map 迭代器组合以构建一个新的迭代器。

1
2
3
4
5
6
const mapRange = mapIterable(range(1, 10), value => value * 10);

for (const num of mapRange) {
if (num > 50) break;
console.log(num);
}

理解和利用 JavaScript 迭代器可以极大地增强您以更优雅、更高效的方式处理数据集合的能力。使用迭代器,您可以简化代码,提高其可读性,并通过一次处理一个元素的数据来减少内存消耗。这个强大的概念使开发人员能够实现自定义迭代行为,使他们的代码更能适应不同的场景。