一、什么是迭代器?
生成器概念在Java,Python等语言中都是具备的,ES6也添加到了JavaScript中。Iterator可以使我们不需要初始化集合,以及索引的变量,而是使用迭代器对象的 next 方法,返回集合的下一项的值,偏向程序化。
迭代器是带有特殊接口的对象。含有一个next()方法,调用返回一个包含两个属性的对象,分别是value和done,value表示当前位置的值,done表示是否迭代完,当为true的时候,调用next就无效了。
ES5中遍历集合通常都是 for循环,数组还有 forEach 方法,对象就是 for-in,ES6 中又添加了 Map 和 Set,而迭代器可以统一处理所有集合数据的方法。迭代器是一个接口,只要你这个数据结构暴露了一个iterator的接口,那就可以完成迭代。ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
二、如何使用迭代器?
1、默认 Iterator 接口
数据结构只要部署了 Iterator 接口,我们就成这种数据结构为“可遍历”(Iterable)。ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 数据,就可以认为是“可遍历的”(iterable)。
可以供 for...of 消费的原生数据结构
- Array
- Map
- Set
- String
- TypedArray(一种通用的固定长度缓冲区类型,允许读取缓冲区中的二进制数据)
- 函数中的 arguments 对象
- NodeList 对象
可以看上面的原生数据结构中并没有对象(Object),为什么呢?
那是因为对象属性的遍历先后顺序是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口就等于部署一种线性变换。
做如下处理,可以使对象供 for...of 消费:
2、调用 Iterator 接口的场合
(1) 解构赋值
(2) 扩展运算符
(3)Generator 函数中的 yield* 表达式(下一章介绍)
(4)其它场合
- for..of
- Array.from
- Map()、Set()、WeakMap()、WeakSet()
- Promise.all()
- Promise.race()
3、for...of 循环的优势
先看看,数组 forEach 方法的缺点:
这个写法的问题在于,无法中途跳出 forEach 循环,break 命令或 return 命令都不能生效。
再看看,对象 for...in 的循环的缺点:
- 数组的键名是数字,但是 for...in 循环是以字符串作为键名,“0”、“1”、“2”等。
- for...in 循环不仅可以遍历数字键名,还会遍历手动添加的期推荐,甚至包括原型链上的键。
- 某些情况下,for...in 循环会议任意顺序遍历键名
- for...in 遍历主要是为遍历对象而设计的,不适用于遍历数组
那么,for...of 有哪些显著的优点呢?
- 有着同 for...in 一样的简洁语法,但是没有 for...in 那些缺点
- 不同于 forEach 方法,它可以与 break、continue 和 return 配合使用
- 提供了遍历所有数据结构的统一操作接口
4、各数据类型如何使用 for...of 循环?
(1)数组
for...of 循环允许遍历数组获得键值
for...of 循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的值
(2)Map 和 Set 结构
由上述的代码可以看出,for...of 循环遍历Map 和 Set 结构时,遍历的顺序是按照各个成员被添加进数据结构的顺序,Set 结构遍历时返回的是一个值,而 Map 结构遍历时返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。
(3)类数组对象
字符串
for (let x of 'a\uD83D\uDC0A') {
console.log(x);
}
// 'a'
// '\uD83D\uDC0A'
DOM NodeList 对象
arguments 对象
没有 Iterator 接口类数组对象的遍历处理
借用 Array.from 方法处理
(4)对象
对于普通对象,不能直接使用 for...of 遍历,否则会报错,必须部署了 Iterator 接口才能使用。如下两种方法部署:
function * entries(obj) {
for (let key of Object.keys(obj)) {
yield[key,obj[key]];
}
}
for (let[key,value] of entries(obj)) {
console.log(key,"->",value);
}
// a -> 1
// b -> 2
// c -> 3
三、迭代器应用实例
1、斐波那契数列
下面我们就使用迭代器来自定义自己的一个斐波那契数列组,我们直到斐波那契数列有两个运行前提,第一个前提是初始化的前两个数字为0,1,第二个前提是将来的每一个值都是前两个值的和。这样我们的目标就是每次都迭代输出一个新的值。
console.log(it.next())
}
//
"value": 0,"done": false
} {
"value": 1,"done": false
} {
"value": 2,"done": false
} {
"value": 3,"done": false
} {
"value": 5,"done": false
}... {
"value": 2584,"done": false
} {
"value": 4181,"done": false
}
2、任务队列迭代器
我们可以定义一个任务队列,该队列初始化时为空,我们将待处理的任务传递后,传入数据进行处理。这样第一次传递的数据只会被任务1处理,第二次传递的只会被任务2处理… 代码如下:
Task.actions.push(function task1(...args) {
console.log("任务一:相乘") return args.reduce(function(x,y) {
return x y
})
},function task2(...args) {
console.log("任务二:相加") return args.reduce(function(x,y) {
return x + y
}) 2
},function task3(...args) {
console.log("任务三:相减") return args.reduce(function(x,y) {
return x - y
})
});
var it = Task[Symbol.iterator]();
console.log(it.next(10,100,2));
console.log(it.next(20,50,100)) console.log(it.next(10,2,1))
//
任务一:相乘 {
"value": 2000,"done": false
}任务二:相加 {
"value": 340,"done": false
}任务三:相减 {
"value": 7,"done": false
}
3、延迟执行
假设我们有一个数据表,我们想按大小顺序依次的获取数据,但是我们又不想提前给他排序,有可能我们根本就不去使用它,所以我们可以在第一次使用的时候再排序,做到延迟执行代码:
next: function() {
if (keys === null) {
keys = Object.keys(_this).sort();
}
return {
value: keys[index],done: index++>keys.length
};
}
}
}
for (var a of table) {
console.log(a)
}
// a b c d
四、结语
本章内容,重点是明白 Iterator 接口的机制,以及 for...of 循环的使用方法。下一章介绍生成器函数 Generator 函数。
原文链接:https://www.f2er.com/js/33776.html