Skip to content

迭代器 iterator

在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。更具体地说,迭代器是通过使用 next() 方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象: value,这是序列中的 next 值;和 done ,如果已经迭代到序列中的最后一个值,则它为 true 。如果 valuedone 一起存在,则它是迭代器的返回值。(来自 CDN)

简单来说,迭代器就是一个帮助我们对某个数据结构(可迭代对象)进行遍历的对象。

可迭代协议

在 JavaScript 中,「迭代器」也是一个具体的对象,这个对象需要符合迭代协议:可迭代协议(Iterable Protocols)迭代器协议(Iterator Protocols)

可迭代协议:表明可迭代

  • 可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为;
  • 要成为可迭代对象, 一个对象必须实现 @@iterator 方法。

迭代器协议:表明迭代方式

  • 迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值;
  • 迭代器含有 next 方法,该方法执行会返回一个包含 done 和 value 的对象。
js
var arr = [1,2];
var arrIt = arr[Symbol.iterator]();
arrIt.next(); // {value: 1, done: false}
arrIt.next(); // {value: 2, done: false}
arrIt.next(); // {value: undefined, done: true}

可迭代对象

Array、String、TypedArray、Map、Set。 他们都有一个键为「@@iterator」的属性,通过Symbol.iterator访问该属性。可以通过 data[Symbol.iterator]()返回一个「迭代器iterator」,调用它的.next()方法进行迭代!

Symbol

ES6 新引入的一种原始数据类型,可返回一个独一无二的值。

如何判断是否可迭代

js
return data[Symbol.iterator] && typof data[Symbol.iterator] ==function' ? true : false;

即满足以下条件:

  1. 有[Symbol.iterator]属性;
  2. [Symbol.iterator]的值为函数;
  3. 函数返回值是一个对象,包含 value 和 done。

使用场景

  • 通过for…of进行遍历;
  • 解构赋值;
  • 扩展运算符( ),可以将任何部署了 Iterator 接口的数据结构,转为数组;
  • 创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);
  • 一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);

实现迭代器

Generator(生成器)生成

js
// A 有限迭代器
let babies = ['hpy', 'zs', 'yxz', 'hfcj', 'zpj', 'cfy', 'yxx', 'lyc', 'zls', 'yxn', 'cym’];

babies[Symbol.iterator] = function* () {
  let index = 0;
  while (index < this.length) {
    yield this[index];
    index++;
  }
};

var bIt = babies[Symbol.iterator]();
console.log(bIt.next()); //{value: 'hpy', done: false}

for (let baby of babies) {
  console.log(baby);
}


// B 无限迭代器
function* idMaker(){
    let index = 0;
    while(true)
        yield index++;
}

let gen = idMaker(); // "Generator { }"
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
// ...

普通函数生成

js
const info = {
  friends: ["aaa", "bbb", "ccc"],
  [Symbol.iterator]() {
    let index = 0;
    const infoIterator = {
      // next方法用箭头函数, 这样next方法中就不再绑定this, this会去上层作用域中寻找, 上层作用域中的this会指向info。
      next: () => {
        if (index < this.friends.length) {
          return { done: false, value: this.friends[index++] };
        } else {
          return { done: true };
        }
      },
    };
    return infoIterator;
  },
}

让对象可迭代 1:[Symbol.iterator]方法返回的对象包含 next方法

js
let range = {
  start: 0,
  end: 3,
  [Symbol.iterator]: function () {
    return {
      next: () => {
        if (this.start > this.end) {
          return { value: undefined, done: true };
        }
        return { value: this.start++, done: false };
      },
    };
  },
};

console.log(range[Symbol.iterator]().next()); //{ value: 0, done: false }

for (let item of range) {
  console.log("range item: ", item);
}
// range item: 0
// range item: 1
// range item: 2
// range item: 3
让对象可迭代 2:[Symbol.iterator]方法和next方法分开定义
js
let range2 = {
  start: 0,
  end: 3,
  [Symbol.iterator]: function () {
    return this;
  },
  next: function () {
    if (this.start > this.end) {
      return { value: undefined, done: true };
    }
    return { value: this.start++, done: false };
  },
};

console.log(range2.next()); //{ value: 0, done: false }
console.log(range2.next()); //{ value: 1, done: false }

for (let item of range2) {
  console.log("range2 item: ", item);
}
// range2 item: 2
// range2 item: 3

_ 关于对象的迭代:如果先调用了next()方法,那么for...of就只会遍历剩余的项。比如这里只会输出range2 item: 2 range2 item: 3_

看一些例子

js
var obj = {};
var arr = [1,2,3,4]
var str = 'abc'
var map = new Map();
map.set('age',30)
var set = new Set([1,2,3])

var sb_a = Symbol('a');
var sb_b = Symbol('b');
console.log('sb_b:', sb_b)

obj[sb_a] = 'a'
obj[sb_b]='b';

console.log(obj)
console.log('obj[sb_a]:', obj[sb_a])
console.log('\n')

console.log(map)
console.log(set)
console.log('\n')

var arrIt = arr[Symbol.iterator]();
console.log('arrIt.next():',arrIt.next())
console.log('arrIt.next():',arrIt.next())
console.log('arrIt.next():',arrIt.next())
console.log('arrIt.next():',arrIt.next())
console.log('arrIt.next():',arrIt.next())
console.log('\n')

var strIt = str[Symbol.iterator]();
console.log('strIt.next():',strIt.next())
console.log('strIt.next():',strIt.next())
console.log('strIt.next():',strIt.next())
console.log('strIt.next():',strIt.next())
console.log('\n')

var mapIt = map[Symbol.iterator]();
console.log('mapIt.next():',mapIt.next())
console.log('mapIt.next():',mapIt.next())
console.log('\n')

var setIt = set[Symbol.iterator]();
console.log('setIt.next():',setIt.next())
console.log('setIt.next():',setIt.next())
console.log('setIt.next():',setIt.next())
console.log('setIt.next():',setIt.next())
console.log('\n')

console.log(typeof set[Symbol.iterator])

a5555d2f131fbdf3f1b20306562f0fe6.png

参考链接

迭代器和生成器-CDNJavaScript 迭代协议JS 迭代器和可迭代对象ES6 symbol 竟然还有这些用途!

Released under the MIT License.