这种写法叫 transduce。有很多工具库提供了 transducer 函数。比如 transducers-js。除了用 transducer 来遍历数组,还能用它来遍历对象和其它数据集。功能相当强大。
六,for 循环和 for … of 循环的区别
for … of 循环是在 ES6 引入 Iterator 后,为了遍历 Iterable 数据类型才产生的。EcmaScript 的 Iterable 数据类型有数组,字符串,Set 和 Map。for … of 循环属于重型的操作(具体细节我也没了解过),如果用 AirBNB 的 ESLint 规则,在代码中使用 for … of 来遍历数组是会被禁止的。
那么,for … of 循环应该在哪些场景使用呢?目前我发现的合理使用场景是遍历自定义的 Iterable。来看这个题目:
问题十八:
将 Stark 家族成员名字遍历,每次遍历暂停一秒,然后将当前遍历的名字打印来,遍历完后回到第一个元素再重新开始,无限循环。
const starks = [ "Eddard Stark", "Catelyn Stark", "Rickard Stark", "Brandon Stark", "Rob Stark", "Sansa Stark", "Arya Stark", "Bran Stark", "Rickon Stark", "Lyanna Stark" ];
答案:
function* repeatedArr(arr) { let i = 0; while (true) { yield arr[i++ % arr.length]; } } const infiniteNameList = repeatedArr(starks); const wait = ms => new Promise(resolve => { setTimeout(() => { resolve(); }, ms); }); (async () => { for (const name of infiniteNameList) { await wait(1000); console.log(name); } })();
七,放弃倔强,实在需要用 for 循环了
前面讲到的问题基本覆盖了大部分需要使用 for 循环的场景。那是否我们可以保证永远不用 for 循环呢?其实不是。我讲了这么多,其实是在鼓励大家不要写 for 循环,而不是不用 for 循环。我们常用的数组原型链上的 map,filter 等高阶函数,底层其实是用 for 循环实现的。在需要写一些底层代码的时候,还是需要写 for 循环的。来看这个例子:
Number.prototype[Symbol.iterator] = function*() { for (let i = 0; i <= this; i++) { yield i; } }; [...6]; // [0, 1, 2, 3, 4, 5, 6]
注意,这个例子只是为了好玩。生产环境中不要直接修改 JS 内置数据类型的原型链。原因是 V8 引擎有一个原型链快速推测机制,修改原型链会破坏这个机制,造成性能问题。