【JavaScript】ES6之迭代器(Iterator)和生成器(Generttor)

为什么需要迭代器

用循环语句迭代数据时,必须要初始化一个变量来记录每次迭代在数据集合中的位置,而在许多编程语言中,已经开始通过程序化的方式用迭代器对象返回迭代过程中集合的每一个元素。迭代器的使用可以极大地简化数据操作,于是ECMAScript6也向JavaScript中添加了这个迭代器特性。新的数组方法和新的集合类型(例如Set集合与Map集合)都依赖迭代器的实现,for-of循环、展开运算符(...。),甚至连异步编程都可以使用选代器。

什么是迭代器

迭代器是一种特殊的对象,所有迭代器对象都有一个next()方法,每次调用会返回一个结果对象。结果对象有两个属性:

  • value: 表示下一个将要返回的值
  • done:布尔类型,有更多可返回数据时返回false,否则返回true

如果在最后一个返回后在调用next()方法,done值为true,value则包含迭代器最终返回的值,这个返回值不是数据集的一部分,与函数的返回值类似,是函数调用过程中最后一次给调用者传递信息的方法,没有相关数据则返回undefined
我们现在可以用ES5语法创建一个迭代器:

function createIterator(items) {
  var i = 0
  return {
    next: function() {
      var done = (i >= items.length)
      var value = !done ? items[i++] : undefined
      return {
        value: value,
        done: done
      }
    }
  }
}

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())

看起来稍微复杂,ES6引入了一个生成器对象,它可以让创建迭代器对象的过程变的更简单。

什么是生成器

生成器是一种返回迭代器的含税,通过function关键字后的星号(*)来表示,函数中还会用到新的的关键字yield

// function *createIterator() {
//   yield 1
//   yield 2
//   yield 3
// }
function *createIterator(items) {
  for (let i = 0; i < items.length; i++) {
    yield items[i]
  }
}
const 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 }

生成器函数也可以用下面的方式创建:

let createIterator = function *(items){}

给对象添加生成器方法:

let o = {
  *createIterator(items) {
    for (let i = 0; i < items.length; i++) {
      yield items[i]
    }
  }
}
const iterator = o.createIterator([1, 2, 3])

注意:不能用箭头函数创建生成器

可迭代对象和for-of循环

可迭代对象Symbol.iterator属性。在ES6中,所有的集合对象(数组、Set集合和Map集合)和字符串都是可迭代对象,这些对象都有默认的迭代器。
for-of循环每执行一次都会调用可迭代对象的next()方法,并将迭代器返回的结果对象的value属性存储在一个变量中,循环在结果对象的done属性为true时结束。

const arr = [1, 2, 3]
for (let item of arr) {
  console.log(item)
}

for-of 用于不可迭代对象、null、undefined时,会抛出错误。

访问默认迭代器

可以通过Symbol.iterator来访问对象默认的迭代器:

const arr = [1, 2, 3]
let iterator = arr[Symbol.iterator]()
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 }

检测对象是否为可迭代对象:

function isIterator(obj) {
  return typeof obj[Symbol.iterator] === 'function'
}

创建可迭代对象

我们创建如下对象,就是一个不可迭代对象:

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

如何使其变成一个可迭代对象呢,代码如下:

let obj = {
  a: 1,
  b: 2,
  c: 3,
  *[Symbol.iterator]() {
    let keys = Object.keys(this)
    for (let i = 0; i < keys.length; i++) {
      yield [keys[i], this[keys[i]]]
    }
  }
}
for (const iterator of obj) {
  console.log(iterator)
}
// 输出如下
/**
[ 'a', 1 ]
[ 'b', 2 ]
[ 'c', 3 ]
**/

内建迭代器

集合对象迭代器

  1. 数组、Map集合、Set集合都有如下三种迭代器:
  • entries()
    返回一个数组,分别表示每个元素的键和值。
    • 数组:第一元素是数字类型的索引
    • Set集合:第一个元素和第二个元素都是值
    • Map集合:第一元素是键名
  • values()
    返回集合中所存的所有值。
  • keys()
    返回集合中存在的每一个键。
  1. 不同集合类型的默认迭代器, 在for-of循环中,如果没有显示的指定则使用默认的迭代器。
  • 数组和Set集合的默认迭代器是 values()
  • Mpa的默认迭代器是entries()
// 数组
let arr = [1, 2, 3]
for (let item of arr) {
 console.log(item)
}
// set集合
let set = new Set([1,2, 3])
for (let item of set) {
 console.log(item)
}
// map集合
let map = new Map()
map.set('name', 'abc')
map.set('age', 12)
for (let [key, value] of map) {
 console.log(`${key}=${value}`)
}

字符串迭代器

let str = 'abcdef'
for (let ch of str) {
  console.log(ch)
}

高级迭代器功能

给迭代器传递参数

如果给迭代器的next()方法传递参数,那么这个参数的值就会替代生成器内部上一条yield语句的返回值。

function *createIterator() {
  let first = yield 1
  let second = yield first + 2 // 10 + 2
  yield second + 3 // 20 + 3
}

const iterator = createIterator()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next(10)) // { value: 12, done: false }
console.log(iterator.next(20)) // { value: 23, done: false }
console.log(iterator.next()) // { value: undefined, done: true }

在迭代器中抛出错误

function *createIterator() {
  let first = yield 1
  let second
  try {
    second = yield first + 2 // 10 + 2
  } catch {
    second = 20
  }
  yield second + 3 // 20 + 3
}

const iterator = createIterator()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next(10)) // { value: 12, done: false }
console.log(iterator.throw(new Error('Some Error'))) // { value: 23, done: false }
console.log(iterator.next()) // { value: undefined, done: true }

生成器返回语句

function *createIterator() {
  yield 1
  return
  yield 2
  yield 3
}

const iterator = createIterator()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: undefined, done: true }
console.log(iterator.next()) // { value: undefined, done: true }
console.log(iterator.next()) // { value: undefined, done: true }

return 也可以指定返回值

function *createIterator() {
  yield 1
  return 10
}

const iterator = createIterator()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 10, done: true }
console.log(iterator.next()) // { value: undefined, done: true }

委托生成器

在某些情况下,我们需要将两个生成器合二为一。

function *createNumberIterator() {
  yield 1
  yield 2
  return 3
}
function *createColorIterator() {
  yield 'blue'
  yield 'red'
}

function *createCombinedIterator() {
  yield *createNumberIterator()
  yield *createColorIterator()
  yield true
}

const iterator = createCombinedIterator()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: 'blue', done: false }
console.log(iterator.next()) // { value: 'red', done: false }
console.log(iterator.next()) // { value: true, done: false }
console.log(iterator.next()) // { value: undefined, done: true }

异步任务执行

function run(taskDef) {
  let task = taskDef()
  // 开始执行任务
  let result = task.next()
  function step() {
    if (!result.done) {
      if (result.value instanceof Promise) {
        result.value.then((value) => {
          result = task.next(value)
          step()
        }).catch((reason) => {
          task.throw(reason)
        })
      } else {
        result = task.next(result.valuve)
        step()
      }
    }
  }
  step()
}

function fetchData(isError = false) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (!isError) {
        resolve('ok')
      } else {
        reject('error')
      }
    }, 2000)
  })
}
run(function*() {
  try {
    let msg1 = yield fetchData()
    console.log(msg1)
    let msg2 = yield fetchData()
    console.log(msg2)
    yield fetchData(true)
  } catch (err) {
    console.log('出错啦:', err)
  }
})

async/await:Promise和生成器的语法糖

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容