基础概念
ES6 表示集合的数据结构有以下四种,并可以组合使用他们,定义自己的数据结构。
- 数组 (Array)
- 对象 (Object)
- Map
- Set
Iterator 是一种机制。它也是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据只要部署 Iterator 接口,就可以完成遍历操作。
Iterator有三个作用
- 为各种数据结构,提供了一个统一的、简便的访问接口
- 使得数据结构的成员能够按某种次序排列
- ES6创造了 for...of 循环
Iterator 遍历的详细过程是
1、创建一个指针对象,指向当前数据结构的起始位置
2、第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员
3、第二次调用指针对象的 next 方法,指针就指向数据结构的第二个成员
4、不断调用 next 方法,直到它指向数据结构的结束位置
下面模拟的 next 的例子
var it = makeIterator(['a', 'b'])
it.next() // {value: 'a', done: false}
it.next() // {value: 'b', done: false}
it.next() // {value: undeifned, done: true}
function makeIterator(array) {
var nextIndex = 0
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true}
}
}
}
默认 Iterator 接口
一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是可遍历的(Iterable)。
大家应该还记得我们在 Symbol 那一章说过的好多属性把,这里 Iterator 接口部署在数据结构的 Symbol.iterator 属性。
eg1:
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
}
如果我们想要给对象能够使用 for...of,原生的 obj 是不能 iterable 的,我们这里可以使用遍历器给他添加,它就可以使用for of 方法了。
var obj = {a: 1, b: 2, c: 3}
for (var item of obj) {
console.log(item)
}
// TypeError: obj is not iterable
-------------------------------------
var obj = {
a: 1,
b: 2,
[Symbol.iterator]: function() {
var iterator = {next: next}
var current = 0
function next() {
if (current < 3) {
return {done: false, value: current++}
} else {
return {done: true}
}
}
return iterator
}
}
for (var item of obj) {
console.log(item)
}
// 0 1 2
原生具备 Iterator 接口的数据结构如下
- Array
- Map
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
下面是一个数组的 Symbol.iterator
let arr = [1, 2, 3]
let iter = arr[Symbol.iterator]()
console.log(JSON.stringify(iter.next()))
// {"value":1,"done":false}
console.log(JSON.stringify(iter.next()))
// {"value":2,"done":false}
console.log(JSON.stringify(iter.next()))
// {"value":3,"done":false}
console.log(JSON.stringify(iter.next()))
// {"done":true}
对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,for...of 循环会自动遍历他们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在 Symbol.iterator 属性上部署,才能被 for...of 循环遍历。
// 类具有 Symbol.iterator
class RangeIterator {
constructor(start, stop) {
this.value = start
this.stop = stop
}
[Symbol.iterator]() {
return this
}
next() {
var value = this.value
if (value < this.stop) {
this.value++
return {done: false, value: value}
}
return {done: true, value: undefined}
}
}
function range(start, stop) {
return new RangeIterator(start, stop)
}
for(var value of range(0, 3)){
console.log(value)
}
// 0 1 2
上面代码是一个类部署有 Iterator 接口,所以 new 出来的这个类的实例化对象是具备 Symbol.iterator 属性的。
下面是通过遍历器实现指针结构的例子
function Obj (value) {
this.value = value
this.next = null
}
Obj.prototype[Symbol.iterator] = function() {
var iterator = { next: next }
var current = this
function next () {
if(current) {
var value = current.value
current = current.next
return { done: false, value: value }
} else {
return { done: true}
}
}
return iterator
}
var one = new Obj(1)
var two = new Obj(2)
var three = new Obj(3)
one.next = two
two.next = three
for (var i of one) {
console.log(i)
}
// 1 2 3
这里我们为对象添加 Iterator 接口的例子
let obj = {
data: [ 'hello', 'world' ],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
};
} else {
return { value: undefined, done: true };
}
}
}
}
}
for (var a of obj) {
console.log(a)
}
// hello
// world
对于类似数组得对象(存在数值键名和 length 属性),部署 Iterator 接口,有一个简单的方法,就是 Symbol.iterator 方法直接引用数组的 Iterator 接口。
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]
// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
[...document.querySelectorAll('div')]
NodeList 对象是类似数组的对象,本身就具有遍历接口的,可以直接遍历。上面代码将它本身的遍历接口改成数组的 Symbol.iterator 属性,看不到任何影响。
下面例子是再类似数组的对象调用数组的 Symbol.iterator 方法的例子
let iterator = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
}
for (let a of iterator) {
console.log(a)
}
// a b c
注意这里对象使用数组的遍历器,是因为是类似数组的对象,key 都是数字,如果是其他的普通对象部署数组的遍历器是没用效果的。
调用 Iterator 接口的场合
(1) 解构赋值,对数组和 Set 解构进行解构赋值时,会默认调用 Symbol.iterator 方法
let set = new Set()
set.add('a').add('b').add('c')
let [x, y] = set
// x => a
// y => b
let [first, ...rest] = set
// first => a
// rest => ['a', 'b']
(2) 扩展运算符(...)也会调用 Iterator 接口
let str = 'hello'
[...str] // ['h', 'e', 'l', 'l', 'o']
(3) yield* yield* 后面跟的是一个可遍历解构,它会调用遍历器
字符串的 Iterator 接口
var someString = 'hi'
typeof someString[Symbol.iterator]
// 'function
var it = someString[Symbol.iterator]()
it.next()
// {value: "h", done: false}
it.next()
// {value: "i, done: false}
it.next()
// {value: undefined, done: true}
上面代码里面,调用了 Symbol.iterator 返回一个遍历器对象,在这个遍历器上可以调用 next 方法,实现对字符串的遍历。
可以覆盖原生的 Symbol.iterator 方法,达到修改遍历器行为的目的。
var str = new String("hi");
[...str] // ["h", "i"]
str[Symbol.iterator] = function() {
return {
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
}
console.log([...str])
console.log(str)
上面代码中,字符串 str的 Symbol.iterator方法被修改了
Iterator 接口与 Generator 函数
Symbol.iterator 方法最简单的实现,可以使用 Generator,这里大概了解下,下一章我们详细学习 Generator。
let myIterable = {
[Symbol.iterator]: function() {
yield 1;
yield 2;
yield 3;
}
}
[...myIterable] // 1 2 3
// 或者
let obj = {
* [Symbol.iterator] () {
yield 'hello';
yield 'world';
}
}
for (let x of obj) {
console.log(x)
}
// 'hello'
// 'world'
遍历对象的 return 和 throw
遍历器对象除了具有 next 方法,还可以有 return 和 throw 方法。如果是你自己写遍历器对象生成函数,那么 next 方法是必须部署的,return 和 throw 是否部署是可选的。
return 方法使用场合是 for...of 循环提前退出,通常是因为出错或者有break 语句,就会调用 return 方法。如果一个对象在完成遍历之前,需要清理或释放资源,就可以部署 return 方法。
function readLinesSync (file) {
return {
[Symbol.iterator]() {
return {
next() {
return {done: false}
},
return() {
file.close()
return {done: true}
}
}
}
}
}
下面这两种情况都会触发执行 return 方法
// 情况一
for (let line of readLinesSync(fileName)) {
console.log(line);
break;
}
// 情况二
for (let line of readLinesSync(fileName)) {
console.log(line);
throw new Error();
}
上面代码中,情况一输出文件第一行后,就会执行 return 方法,关闭文件。 情况二会执行 return 方法关闭文件后,在抛出错误。
throw 方法主要配合 Generator 函数使用。