ES6 的 Iterator (迭代器)

概念

参考阮一峰老师的ES6

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了 Map Set 。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是 Map,Map 的成员是对象。这样就 需要一种统一的接口机制,来处理所有不同的数据结构

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator 的作用有三个:

  1. 一 是为各种数据结构,提供一个统一的、简便的访问接口;
  2. 二 是使得数据结构的成员能够按某种次序排列;
  3. 三 是 ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费

for of 遍历

Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即 for...of 循环(详见下文)。当使用for...of 循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。

一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。

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

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

为对象添加 Iterator

简单例子

let obj = {
  data:  ['a', 'b', 'c'],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        }
        return { value: undefined, done: true };
      }
    };
  }
};
for (let item of obj) {
  console.log(item); // 'a', 'b', 'c'
}

使用 yield

Symbol.iterator() 方法的 最简单实现,使用 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"

Symbol.iterator() 方法几乎不用部署任何代码,只要用 yield 命令给出每一步的返回值即可。 


类似数组对象调用 Symbol.iterator

类似数组的对象, 前面的键 类似 数组的索引 (0 1 2... ) , 所以可以通过 for of 遍历

let iterable = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
  console.log(item); // 'a', 'b', 'c'
}

注意,普通对象部署数组的 Symbol.iterator 方法,并无效果。

let iterable = {
  a: 'a',
  b: 'b',
  c: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
  console.log(item); // undefined, undefined, undefined
}

调用 Iterator 接口的场合

有一些场合会默认调用 Iterator 接口(即 Symbol.iterator 方法),除了 for...of 循环,还有几个别的场合。

解构赋值

数组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']

上面代码的扩展运算符内部就调用 Iterator 接口。

实际上,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构转为数组。

也就是说,只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组

let arr = [...iterable];

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 }

其他场合

由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。

下面是一些例子

  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
  • Promise.all()
  • Promise.race()

数组遍历的一些方法

for 循环    forEach   for in   for of

forEach

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


for in

for...in 循环可以遍历数组的键名

for...in 循环有几个缺点。

  • 数组的键名是数字,但是 for...in 循环是以字符串作为键名 “0”、“1”、“2” 等等。
  • for...in 循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键
  • 某些情况下,for...in 循环会以任意顺序遍历键名。

总之,for...in 循环主要是为遍历对象而设计的,不适用于遍历数组。

let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
  console.log(i); // "0", "1", "2", "foo"
}

for of

  • 有着同  for...in 一样的简洁语法,但是没有 for...in 那些缺点。
  • 不同于 forEach 方法,它可以与 breakcontinue return 配合使用。
  • 提供了遍历所有数据结构的统一操作接口。

下面是一个使用 break 语句,跳出 for...of 循环的例子

for (var n of fibonacci) {
  if (n > 1000)
    break;
  console.log(n);
}

上面的例子,会输出斐波纳契数列小于等于 1000 的项。如果当前项大于 1000

就会使用 break 语句跳出 for...of 循环。


注意(对象)

对象没有部署 Iterator 接口, 但是对象可以进行 解构赋值扩展运算符 ... 等操作 

解构赋值

let obj = {
  a: 1,
  b: 2,
  c: 3
};

const {a, b} = obj;
console.log(a, b)  // 1 2

... 扩展运算符

let obj = {
  a: 1,
  b: 2,
  c: 3
};

const a = {...obj};
console.log(a)  // {a: 1, b: 2, c: 3}

原因

对象解构和 Iterator 迭代器没关系

对象的解构就是: 创建新对象-> 枚举属性 -> 复制属性并赋值


文章如有错误,恳请大家提出问题,本人不胜感激 。 不懂的地方可以评论,我都会 一 一 回复

文章对大家有帮助的话,希望大家能动手点赞鼓励,大家未来一起努力 长路漫漫,道阻且长

猜你喜欢

转载自blog.csdn.net/qq_52855464/article/details/130793770