Iterator 定义
在JavaScript中,表示集合的数据结构只要是:Array
和 Object
,在ES6中添加了Map
和Set
。Iterator (遍历器)为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以进行遍历操作。(即:依次处理这个数据结构的所有成员)
Iterator 的作用
- 为各种数据结构提供统一、比娜姐的访问接口。
- 是的数据结构的成员按照某种次序排列
Iterator
接口主要提供与for...of
使用
Iterator 的遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置
- 调用指针对象的
next
方法,将指针指向数据结构的下一个成员 - 不断调用指针对象的
next
方法,知道它指向数据结构的结束位置
指针的next
方法返回一个对象,表示当前数据成员的信息。这个对象有value
(返回当前位置的成员)和done
(表示遍历是否解释)两个属性。对于Iterator(遍历器)来说,done:false
和value:undefined
属性都是可以省略的。遍历器与它所遍历的数据结构是分开的,完全可以写出没有对应数据结构的遍历对象。
默认Iterator接口
当使用for...of
循环遍历某种数据结构时,这个循环自动去寻找Iterator接口。
ES6中,默认的Iterator接口部署在数据结构的Symbol.iterator
属性,或者具有Symbol.iterator
属性,就认为是“可遍历的”。
原生具有Iterator接口的数据结构
- Array
- Map
- Set
- String
- TypedArray
- 函数的arguments对象
- NodeList对象
对象为什么没有Iterator接口
对象(Object)没有Iterator接口,是因为不确定对象属性的遍历顺序,需要手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,相当于是将非线性的数据结构转换为线性数据结构。
如果一个对象可以被for...of
循环调用,就必须在Symbol.iterator
的属性上部署遍历器的生成方法或者在原型链上具有也可以。
如果
Symbol.iterator
方法对应的不是遍历器生成函数,解释引擎将会报错。
调用Iterator接口的具体应用
解构赋值
对数组和Set结构进行解构赋值时,默认会调用Symbol.iterator
方法
const set = new Set().add('a').add('b').add('c');
const [x,y] = set;
console.log(x); // 'a'
console.log(y); // 'b'
const [first, ...rest] = set;
console.log(first); // 'a'
console.log(rest); // ['b','c']
扩展运算符
扩展运算符(...
)也会调用默认的Iterator接口
const str = 'abc';
console.log([...str]); // ['a','b','c']
yield*
在yield*
后面跟着一个可遍历的结构,他会调用这个结构的遍历接口。
const generator = function*(){
yield 1;
yield* [2,3,4];
};
const iterator = generator();
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
其他
for...in
Array.from()
Map()
、Set()
、WeakMap()
、WeakSet()
Promise.all()
Promise.race()
字符串的Iterator接口
字符串是一个类似数组的对象,具有原生Iterator接口
const str = 'abc';
console.log(typeof str[Symbol.iterator]); // 'function'
const iterator = str[Symbol.iterator];
console.log(iterator.next()); // {value: 'a', done: false}
console.log(iterator.next()); // {value: 'b', done: false}
Iterator(遍历器)对象的return()、throw()
return() 使用场景
- 当
for...of
循环提前退出(出错或者有break
语句),此时Iterator(遍历器)对象就会调用return()
方法. - 如果一个对象在完成遍历前,需要清理或者释放资源,就可以部署
return
方法
方法
return()
方法必须返回的是一个对象,这个Generator规格决定的
throw() 使用场景
方法throw()
主要是配合Generator函数使用。
for…in 和 for…of 的比较
- 对于普通对象,
for...of
不能直接使用,会报错。for...in
循环可以用来遍历键名
-
此时可以使用
Object.keys
方法将对象的键名生成一个数据,然后遍历这个数组 -
使用Generator 函数将这个普通对象重新包装一下
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)
}
-
数组的键名是数字,但是
for...in
循环是以字符串作为键名。 -
for...in
循环会手动添加其他键,甚至包括原型链上的键 -
某些情况下,
for...in
循环会以任意顺序遍历键名 -
for...of
不同于forEach
,可以与break
、continue
和return
配合使用 -
for...of
提供了便利所有数据结构的统一接口操作
备注:本文是自己学习阮一峰老师的《ECMAScript 6 入门》所做的笔记,大部分例子来源于此书。