《深入理解ES6》笔记——迭代器(Iterator)和生成器(Generator)(8)

玉官
• 阅读 3569

迭代器(Iterator)

ES5实现迭代器

迭代器是什么?遇到这种新的概念,莫慌张。

迭代器是一种特殊对象,每一个迭代器对象都有一个next(),该方法返回一个对象,包括value和done属性。

ES5实现迭代器的代码如下:

//实现一个返回迭代器对象的函数,注意该函数不是迭代器,返回结果才叫做迭代器。
function createIterator(items) {
  var i = 0;
  return {
    next() {
      var done = (i >= items.length); // 判断i是否小于遍历的对象长度。
      var value = !done ? items[i++] : undefined; //如果done为false,设置value为当前遍历的值。
      return {
        done,
        value
      }
    }
  }
}
const a = createIterator([1, 2, 3]);

//该方法返回的最终是一个对象,包含value、done属性。
console.log(a.next()); //{value: 1, done: false}
console.log(a.next()); //{value: 2, done: false}
console.log(a.next()); //{value: 3, done: false}
console.log(a.next()); //{value: undefined, done: true}

生成器(Generator)

生成器是函数:用来返回迭代器。

这个概念有2个关键点,一个是函数、一个是返回迭代器。这个函数不是上面ES5中创建迭代器的函数,而是ES6中特有的,一个带有*(星号)的函数,同时你也需要使用到yield。

//生成器函数,ES6内部实现了迭代器功能,你要做的只是使用yield来迭代输出。
function *createIterator() {
  yield 1;
  yield 2;
  yield 3;
}
const a = createIterator();
console.log(a.next()); //{value: 1, done: false}
console.log(a.next()); //{value: 2, done: false}
console.log(a.next()); //{value: 3, done: false}
console.log(a.next()); //{value: undefined, done: true}

生成器的yield关键字有个神奇的功能,就是当你执行一次next(),那么只会执行一个yield后面的内容,然后语句终止运行。

在for循环中使用迭代器

即使你是在for循环中使用yield关键字,也会暂停循环。

function *createIterator(items) {
  for(let i = 0; i < items.length;  i++) {
    yield items[i]
  }
}
const a = createIterator([1, 2, 3]);
console.log(a.next()); //{value: 1, done: false}

yield使用限制

yield只可以在生成器函数内部使用,如果在非生成器函数内部使用,则会报错。

function *createIterator(items) {
    //你应该在这里使用yield
  items.map((value, key) => {
    yield value //语法错误,在map的回调函数里面使用了yield
  })
}
const a = createIterator([1, 2, 3]);
console.log(a.next()); //无输出

生成器函数表达式

函数表达式很简单,就是下面这种写法,也叫匿名函数,不用纠结。

const createIterator = function *() {
    yield 1;
    yield 2;
}
const a = createIterator();
console.log(a.next());

在对象中添加生成器函数

一个对象长这样:

const obj = {}

我们可以在obj中添加一个生成器,也就是添加一个带星号的方法:

const obj = {
  a: 1,
  *createIterator() {
    yield this.a
  }
}
const a = obj.createIterator();
console.log(a.next());  //{value: 1, done: false}

可迭代对象和for of循环

再次默读一遍,迭代器是对象,生成器是返回迭代器的函数。

凡是通过生成器生成的迭代器,都是可以迭代的对象(可迭代对象具有Symbol.iterator属性),也就是可以通过for of将value遍历出来。

function *createIterator() {
  yield 1;
  yield 2;
  yield 3;
}
const a = createIterator();
for(let value of a) {
  console.log(value)
}
// 1 2 3

上面的例子告诉我们生成器函数返回的迭代器是一个可以迭代的对象。其实我们这里要研究的是Symbol.iterator的用法。

function *createIterator() {
  yield 1;
  yield 2;
  yield 3;
}
const a = createIterator(); //a是一个迭代器
const s = a[Symbol.iterator]();//使用Symbol.iterator访问迭代器
console.log(s.next()) //{value: 1, done: false}

Symbol.iterator还可以用来检测一个对象是否可迭代:

typeof obj[Symbol.iterator] === "function"

创建可迭代对象

在ES6中,数组、Set、Map、字符串都是可迭代对象。

默认情况下定义的对象(object)是不可迭代的,但是可以通过Symbol.iterator创建迭代器。

const obj = {
  items: []
}
obj.items.push(1);//这样子虽然向数组添加了新元素,但是obj不可迭代
for (let x of obj) {
  console.log(x) // _iterator[Symbol.iterator] is not a function
}

//接下来给obj添加一个生成器,使obj成为一个可以迭代的对象。
const obj = {
  items: [],
  *[Symbol.iterator]() {
    for (let item of this.items) {
      yield item;
    }
  }
}
obj.items.push(1)
//现在可以通过for of迭代obj了。
for (let x of obj) {
  console.log(x)
}

内建迭代器

上面提到了,数组、Set、Map都是可迭代对象,即它们内部实现了迭代器,并且提供了3种迭代器函数调用。

1、entries() 返回迭代器:返回键值对

//数组
const arr = ['a', 'b', 'c'];
for(let v of arr.entries()) {
  console.log(v)
}
// [0, 'a'] [1, 'b'] [2, 'c']

//Set
const arr = new Set(['a', 'b', 'c']);
for(let v of arr.entries()) {
  console.log(v)
}
// ['a', 'a'] ['b', 'b'] ['c', 'c']

//Map
const arr = new Map();
arr.set('a', 'a');
arr.set('b', 'b');
for(let v of arr.entries()) {
  console.log(v)
}
// ['a', 'a'] ['b', 'b']

2、values() 返回迭代器:返回键值对的value

//数组
const arr = ['a', 'b', 'c'];
for(let v of arr.values()) {
  console.log(v)
}
//'a' 'b' 'c'

//Set
const arr = new Set(['a', 'b', 'c']);
for(let v of arr.values()) {
  console.log(v)
}
// 'a' 'b' 'c'

//Map
const arr = new Map();
arr.set('a', 'a');
arr.set('b', 'b');
for(let v of arr.values()) {
  console.log(v)
}
// 'a' 'b'

3、keys() 返回迭代器:返回键值对的key

//数组
const arr = ['a', 'b', 'c'];
for(let v of arr.keys()) {
  console.log(v)
}
// 0 1 2

//Set
const arr = new Set(['a', 'b', 'c']);
for(let v of arr.keys()) {
  console.log(v)
}
// 'a' 'b' 'c'

//Map
const arr = new Map();
arr.set('a', 'a');
arr.set('b', 'b');
for(let v of arr.keys()) {
  console.log(v)
}
// 'a' 'b'

虽然上面列举了3种内建的迭代器方法,但是不同集合的类型还有自己默认的迭代器,在for of中,数组和Set的默认迭代器是values(),Map的默认迭代器是entries()。

for of循环解构

对象本身不支持迭代,但是我们可以自己添加一个生成器,返回一个key,value的迭代器,然后使用for of循环解构key和value。

const obj = {
  a: 1,
  b: 2,
  *[Symbol.iterator]() {
    for(let i in obj) {
      yield [i, obj[i]]
    }
  }
}
for(let [key, value] of obj) {
  console.log(key, value)
}
// 'a' 1, 'b' 2

字符串迭代器

const str = 'abc';
for(let v of str) {
  console.log(v)
}
// 'a' 'b' 'c'

NodeList迭代器

迭代器真是无处不在啊,dom节点的迭代器你应该已经用过了。

const divs = document.getElementByTagName('div');
for(let d of divs) {
  console.log(d)
}

展开运算符和迭代器

const a = [1, 2, 3];
const b = [4, 5, 6];
const c = [...a, ...b]
console.log(c) // [1, 2, 3, 4, 5, 6]

高级迭代器功能

你说什么?上面讲了一堆废话都是基础功能?还有高级功能没讲?

高级功能不复杂,就是传参、抛出异常、生成器返回语句、委托生成器。

1、传参

生成器里面有2个yield,当执行第一个next()的时候,返回value为1,然后给第二个next()传入参数10,传递的参数会替代掉上一个next()的yield返回值。在下面的例子中就是first。

function *createIterator() {
  let first = yield 1;
  yield first + 2;
}
let i = createIterator();
console.log(i.next()); // {value: 1, done: false}
console.log(i.next(10)); // {value: 12, done: false}

2、在迭代器中抛出错误

function *createIterator() {
  let first = yield 1;
  yield first + 2;
}
let i = createIterator();
console.log(i.next()); // {value: 1, done: false}
console.log(i.throw(new Error('error'))); // error
console.log(i.next()); //不再执行

3、生成器返回语句

生成器中添加return表示退出操作。
function *createIterator() {
let first = yield 1;
return;
yield first + 2;
}
let i = createIterator();
console.log(i.next()); // {value: 1, done: false}
console.log(i.next()); // {value: undefined, done: true}

4、委托生成器

生成器嵌套生成器

function *aIterator() {
  yield 1;
}
function *bIterator() {
  yield 2;
}
function *cIterator() {
  yield *aIterator()
  yield *bIterator()
}

let i = cIterator();
console.log(i.next()); // {value: 1, done: false}
console.log(i.next()); // {value: 2, done: false}

异步任务执行器

ES6之前,我们使用异步的操作方式是调用函数并执行回调函数。

书上举的例子挺好的,在nodejs中,有一个读取文件的操作,使用的就是回调函数的方式。

var fs = require("fs");
fs.readFile("xx.json", function(err, contents) {
  //在回调函数中做一些事情
})

那么任务执行器是什么呢?

任务执行器是一个函数,用来循环执行生成器,因为我们知道生成器需要执行N次next()方法,才能运行完,所以我们需要一个自动任务执行器帮我们做这些事情,这就是任务执行器的作用。

下面我们编写一个异步任务执行器。

//taskDef是一个生成器函数,run是异步任务执行器
function run(taskDef) {
  let task = taskDef(); //调用生成器
  let result = task.next(); //执行生成器的第一个next(),返回result
  function step() {
    if(!result.done) {
    //如果done为false,则继续执行next(),并且循环step,直到done为true退出。
      result = task.next(result.value);
      step();
    }
  }
  step(); //开始执行step()
}

测试一下我们编写的run方法,我们不再需要console.log N个next了,因为run执行器已经帮我们做了循环执行操作:

run(function *() {
  let value = yield 1;
  value = yield value + 20;
  console.log(value) // 21
})

总结

本章讲了3个概念,迭代器、生成器、任务执行器。

迭代器是一个对象。

生成器是一个函数,它最终返回迭代器。

任务执行器一个函数(或者也叫生成器的回调函数),帮我们自动执行生成器的内部运算,最终返回迭代器。

不知道看到这里,你明白3者的区别和用法没?

=> 返回文章列表

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Stella981 Stella981
4年前
SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器!复制代码(https://oscimg.oschina.net/oscnet/675f5689159acfa2c39c91f4df40a00ce0f.gif)/cookie对象;rememberMeCookie()方法是设置Cookie的生成模
Stella981 Stella981
4年前
Python三大神器之迭代器详解
我们将要来学习python的重要概念迭代和迭代器,通过简单实用的例子如列表迭代器和xrange。可迭代一个对象,物理或者虚拟存储的序列。list,tuple,strins,dicttionary,set以及生成器对象都是可迭代的,整型数是不可迭代的。如果你不确定哪个可迭代哪个不可以,你需要用python内建的iter()来帮忙。
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
4年前
SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器!复制代码(https://oscimg.oschina.net/oscnet/675f5689159acfa2c39c91f4df40a00ce0f.gif)/cookie对象;rememberMeCookie()方法是设置Cookie的生成模
Stella981 Stella981
4年前
Php5.5新特性 Generators详解
在PHP5.5.0版本中,新增了生成器\(Generators)_特性,用于简化实现迭代器接口_(Iterator)\创建简单的迭代器的复杂性。通过生成器,我们可以轻松的使用foreach迭代一系列的数据,而不需要事先在内存中构建要被迭代的对象,大大减少了内存开销。当生成器函数被调用的时候,它会返回一个可迭代的对象,当对该对象进行迭代
Wesley13 Wesley13
4年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Stella981 Stella981
4年前
Python 迭代器与生成器
python迭代器与生成器说到python迭代器,首先要明确两个概念:Iterable和Iterator,这两个概念还有Generator都是定义在collections模块里的。Iterable意为“可迭代的(对象)”,包括如下两种:1、实现了__getitem__(self,
Stella981 Stella981
4年前
OpenCV访问像素点
三种方法迭代器创建一个Mat::Iterator对象it,通过itMat::begin()来的到迭代首地址,递增迭代器知道itMat::end()结束迭代;while(it!Scr.end<Vec3b()){//(it)00;//蓝色通道置零;