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

2023-04-29  本文已影响0人  远人村c

为什么需要迭代器

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

什么是迭代器

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

如果在最后一个返回后在调用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集合都有如下三种迭代器:
  1. 不同集合类型的默认迭代器, 在for-of循环中,如果没有显示的指定则使用默认的迭代器。
// 数组
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和生成器的语法糖

上一篇 下一篇

猜你喜欢

热点阅读