Iterator
一、概念
任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
iterator的三个作用:
- 为各种数据结构,提供一个统一的、简便的访问接口
- 使得数据结构的成员能够按某种次序排列
- ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费
ES6 的有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被 for...of 循环遍历。原因在于,这些数据结构原生部署了 Symbol.iterator 属性(详见下文),另外一些数据结构没有(比如对象)。凡是部署了 Symbol.iterator 属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
原生具备 Iterator 接口的数据结构如下:
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
let str = "hello world";
let iterators = str[Symbol.iterator](); // 有一个Symbol.iterator属性,是一个返回值为迭代器的函数
console.log(iterators)
while(true){
let e = iterators.next();
if(e.done){
break;
}
console.log(e,e.value);
}
对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数, for...of 循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在 Symbol.iterator 属性上面部署,这样才会被 for...of 循环遍历。
对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用,ES5 没有 Map 结构,而 ES6 原生提供了。
一个对象如果要具备可被 for...of 循环调用的 Iterator 接口,就必须在 Symbol.iterator 的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
// 手写一个类,实现可迭代
class Range {
start;
stop;
current;
constructor(start, stop) {
this.start = start;
this.stop = stop;
this.current = start;
}
[Symbol.iterator]() {
return this;
}
next(){
let done = this.current >this.stop;
let result = {value:this.current,done};
this.current++;
return result;
}
}
const range = new Range(2, 10);
const iter = range[Symbol.iterator]();
while (true) {
let e = iter.next();
if (e.done) {
break;
}
console.log(e.value)
}
二、调用 Iterator 接口的场合
1.解构赋值
2.扩展运算符
3.yield*
4.字符串的 Iterator 接口
// 对象的遍历
var obj = {
name:"hello",
age:18,
sex:'男'
}
// for in
for(let e in obj){
console.log(e)
}
// for of
for(let value of obj){ // error 对象不是可迭代的,没有iterator接口
console.log(value)
}
// 数组的遍历
var arr = ["a","b","c"];
// for in
for(let e in arr){
console.log(e,typeof e) // 数组是一个对象,这里输出的键值是数组的索引,
// 注意key是String类型。对象的键是String类型的。
}
for(let e of arr){
console.log(e) // 直接输出值
}
上面代码表明, **for...in 循环读取键名, for...of 循环读取键值。**如果要通过 for...of 循环,获取数组的索引,可以借助数组实例的 entries 方法和 keys 方法。
Set 和 Map 结构:
let set = new Set();
set.add("hello");
set.add("world");
set.add("computer")
for (let e of set){
console.log(e)
}
let map = new Map();
map.set("red","红色");
map.set("blue","蓝色");
map.set("green","绿色");
for(let entry of map){ // next方法value值是数组
console.log(entry instanceof Array)
console.log(entry)
}
for(let [k,v] of map){
console.log(k,v)
}
首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。
for...in 循环有几个缺点:
- 数组的键名是数字,但是 for...in 循环是以字符串作为键名“0”、“1”、“2”等等。
- for...in 循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
- 某些情况下, for...in 循环会以任意顺序遍历键名。
总之, for...in 循环主要是为遍历对象而设计的,不适用于遍历数组。
for...of 循环相比上面几种做法,有一些显著的优点。
- 有着同 for...in 一样的简洁语法,但是没有 for...in 那些缺点。
- 不同于 forEach 方法,它可以与 break 、 continue 和 return 配合使用。
- 提供了遍历所有数据结构的统一操作接口。