ES6学习笔记(17)之 Iterator 和 for...of 循环

参考:ECMAScript 6 入门

简单介绍:

我的理解:所谓可遍历,就是可以取到下一个。重点在于实现一个可以取到下一个对象的next方法

首先,Iterator适合于容器对象,既可以存储其它对象的对象,比如:Array, Map, Set等。
其内部通过next方法可以不断获取存储的里面的下一个对象,实现了这样的next方法的对象便是可遍历的。
严谨一点,一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。

Iterator接口应该长什么样儿的?使用TypeScript的写法如下:

interface Iterable {
  [Symbol.iterator]() : Iterator,
}

interface Iterator {
  // next方法是重点,通过next方法来获取下一个对象
  next(value?: any) : IterationResult,
}

interface IterationResult {
  value: any,
  done: boolean,
}

ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。

原生具备 Iterator 接口的数据结构如下

Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象

这些数据结构原生部署了Symbol.iterator属性

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator](); //调用Array对象的Symbol.iterato属性方法生成遍历器

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
Screen Shot 2019-09-09 at 3.45.01 PM.png

除了for...of,还有哪些是使用iterator的变种(请注意,Object对象默认没有部署 Iterator 接口)

  • 解构赋值:对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
let set = new Set().add('a').add('b').add('c');

let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b','c'];
  • 扩展运算符:扩展运算符(...)也会调用默认的 Iterator 接口。
// 例一
var str = 'hello';
[...str] //  ['h','e','l','l','o']

// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
  • yield:yield后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
let generator = function* () {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

var iterator = generator();

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
  • 字符串对象也实现了 Iterator 接口(我的理解,字符串本质上就是一个char数组,所以它可以被遍历也是情理之中的了)
var someString = "hi";
var iterator = someString[Symbol.iterator]();

iterator.next()  // { value: "h", done: false }
iterator.next()  // { value: "i", done: false }
iterator.next()  // { value: undefined, done: true }

使用 Generator 函数实现遍历接口

let myIterable = {
  [Symbol.iterator]: function* () {
    yield 1; // yield 是产出的意思
    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"

其它遍历器对象:

let map = new Map().set('a', 1).set('b', 2);
for (let pair of map) {
  console.log(pair);
}
// ['a', 1]
// ['b', 2]

像上面的例子,map应该调用了它默认的遍历生成器。如果我只想遍历map的key或者value怎么办呢?
答案是通过:entries(), keys(), values()等。

ES6 的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象。

  • entries() 返回一个遍历器对象,用来遍历并返回[键名, 键值]组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用entries方法。
  • keys() 返回一个遍历器对象,用来遍历所有的键名。
  • values() 返回一个遍历器对象,用来遍历所有的键值。

类数组对象举例:

let arrayLike = { length: 2, 0: 'a', 1: 'b' };//类数组对象

// 报错
for (let x of arrayLike) {
  console.log(x);
}

// 正确,先使用Array.from将其转为带 Iterator 接口的数组
for (let x of Array.from(arrayLike)) {
  console.log(x);
}

无法中途跳出forEach循环,break命令或return命令都不能奏效。


非重点:

遍历器对象的 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 };
        }
      };
    },
  };
}

上面代码中,函数readLinesSync接受一个文件对象作为参数,返回一个遍历器对象,其中除了next方法,还部署了return方法。下面的两种情况,都会触发执行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方法关闭文件之后,再抛出错误。
注意,return方法必须返回一个对象,这是 Generator 规格决定的。
throw方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。


你可能感兴趣的:(ES6学习笔记(17)之 Iterator 和 for...of 循环)