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