迭代器和生成器

详细的版本在https://my.oschina.net/u/3970421/blog/2993575

由于原文已经够详细了,在这里只是概括一些内容而已。

1.迭代器和生成器的引入

其实就是为了方便循环遍历。

(1)for循环

var colors = ["red", "green", "blue"];
for (var i = 0, len = colors.length; i < len; i++) {
 console.log(colors[i]);
}

虽然循环语句语法简单,但如果将多个循环嵌套则需要追踪多个变量,代码复杂度会大大增加,一不小心就错误使用了其他for循环的跟踪变量,从而导致程序出错。迭代器的出现旨在消除这种复杂性并减少循环中的错误 。

(2)迭代器

迭代器是一种特殊对象,它具有一些专门为迭代过程设计的专有接口,所有的迭代器对象都有一个next()方法,每次调用都返回一个结果对象。结果对象有两个属性:一个是value,表示下一个将要返回的值;另一个是done,它是一个布尔类型的值,当没有更多可返回数据时返回true。迭代器还会保存一个内部指针,用来指向当前集合中值的位置,每调用一次next()方法,都会返回下一个可用的值。

创建一个迭代器

function createIterator(items) {
 let i = 0;
 return {
  next: function() {
   var done = (i >= items.length);
   var value = !done ? items[i++] : undefined;
   return {
    done: done,
    value: value
   };
  }
 };
}
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"

(3)生成器

生成器就是为了创建迭代器对象的过程变得更简单。

生成器是一种返回迭代器函数,通过function关键字后的星号(*)来表示,函数中会用到新的关键字yield,可以通过它来指定调用迭代器的next()方法时的返回值及返回顺序。星号可以紧挨着function关键字,也可以在中间添加一个空格。

// 生成器
function *createIterator() {
 yield 1;
 yield 2;
 yield 3;
}
// 生成器能像正规函数那样被调用,但会返回一个迭代器
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

注意:每当执行完一条yield语句后函数就会自动停止执行。举个例子,在上面这段代码中,执行完语句yield 1之后,函数便不再执行其他任何语句,直到再次调用迭代器的next()方法才会继续执行yield 2语句。

使用yield关键字可以返回任何值或表达式,所以可以通过生成器函数批量地给迭代器添加元素。例如,可以在循环中使用yield关键字

function *createIterator(items) {
 for (let i = 0; i < items.length; i++) {
  yield items[i];
 }
}
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"

yield使用限制

function *createIterator(items) {
 items.forEach(function(item) {
  // 语法错误
  yield item + 1;
 });
}

从字面上看,yield关键字确实在createlterator()函数内部,但是它与return关键字一样,二者都不能穿透函数边界。嵌套函数中的return语句不能用作外部函数的返回语句,而此处嵌套函数中的yield语句会导致程序抛出语法错误 。

2.可迭代对象

可迭代对象具有Symbol.iterator属性,是一种与迭代器密切相关的对象。

Symbol.iterator通过指定的函数可以返回一个作用于附属对象的迭代器

[注意]由于生成器默认会为Symbol.iterator属性赋值,因此所有通过生成器创建的迭代器都是可迭代对象。

在ES6中,所有的集合对象(数组、Set集合及Map集合)和字符串都是可迭代对象,这些对象中都有默认的迭代器。ES6中新加入的特性for-of循环需要用到可迭代对象的这些功能

let values = [1, 2, 3];
for (let num of values) {
 //1
 //2
 //3
 console.log(num);
}

这段for-of循环的代码通过调用values数组的Symbol.iterator方法来获取迭代器,这一过程是在JS引擎背后完成的。随后迭代器的next()方法被多次调用,从其返回对象的value属性读取值并存储在变量num中,依次为1、2和3,当结果对象的done属性值为true时循环退出,所以num不会被赋值为undefined

相比传统的for循环,for-of循环的控制条件更简单,不需要追踪复杂的条件,所以更少出错,适合只需迭代数组或集合中的值的情况。

注意:for-of语句只能用于遍历可迭代对象的元素,遍历null或undefined将会导致程序抛出错误

访问默认迭代器

可以直接手动调用Symbol.iterator方法来获取迭代器

let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();
console.log(iterator.next()); // "{ value: 1, done: false }"

由于具有Symbol.iterator属性的对象都有默认的迭代器,因此可以用它来检测对象是否为可迭代对象

function isIterable(object) {
 return typeof object[Symbol.iterator] === "function";
}
console.log(isIterable([1, 2, 3])); // true
console.log(isIterable("Hello")); // true
console.log(isIterable(new Map())); // true
console.log(isIterable(new Set())); // true
console.log(isIterable(new WeakMap())); // false
console.log(isIterable(new WeakSet())); // false

创建可迭代对象

默认情况下,我们定义的对象都是不可迭代对象,但因为可迭代对象都有Symbol.iterator属性,如果通过调用Symbol.iterator方法获取一个生成器,则可以将其变为可迭代对象 。(for-of会自动调用Symbol.iterator方法返回一个生成器)

let collection = {
 items: [],
 *[Symbol.iterator]() {
  for (let item of this.items) {
   yield item;
  }
 }
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
 //1
 //2
 //3
 console.log(x);
}

3.展开运算符(...)

let set = new Set([1, 2, 3, 3, 3, 4, 5]),
array = [...set];
console.log(array); // [1,2,3,4,5]

展开运算符可以操作所有可迭代对象,并根据默认迭代器来选取要引用的值,从迭代器读取所有值。然后按照返回顺序将它们依次插入到数组中。

4.内建迭代器

在ES6中有3种类型的集合对象:数组、Map集合与Set集合

为了更好地访问对象中的内容,这3种对象都内建了以下三种迭代器

  • entries() 返回一个迭代器,其值为多个键值对
  • values() 返回一个迭代器,其值为集合的值
  • keys() 返回一个迭代器,其值为集合中的所有键名

调用以上3个方法都可以访问集合的迭代器

let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();
data.set("title", "Understanding ES6");
data.set("format", "ebook");
for (let entry of colors.entries()) {
 console.log(entry);
}
for (let entry of tracking.entries()) {
 console.log(entry);
}
for (let entry of data.entries()) {
 console.log(entry);
}
//[0, "red"]
//[1, "green"]
//[2, "blue"]
//[1234, 1234]
//[5678, 5678]
//[9012, 9012]
//["title", "Understanding ES6"]
//["format", "ebook"]

注意:数组的键为数字类型的索引,Set集合中的值被同时作为键与值使用 。

每个集合类型都有一个默认的迭代器,在for-of循环中,如果没有显式指定则使用默认的迭代器。数组和Set集合的默认迭代器是values()方法,Map集合的默认迭代器是entries()方法。有了这些默认的迭代器,可以更轻松地在for-of循环中使用集合对象

posted @ 2021-02-07 00:46  Hhhighway  阅读(67)  评论(0编辑  收藏  举报