迭代器和生成器

2022/3/12 JavaScriptIteratorGenerator

# 迭代器的定义

迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节

  • 其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中;

  • 在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等;

从迭代器的定义我们可以看出来,迭代器是帮助我们对某个数据结构进行遍历的对象。 在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol):

  • 迭代器协议定义了产生一系列值(无论是优先还是无限个)的标准方式;

  • 那么在js中这个标准就是一个特定的next方法

next方法有如下的要求:

  • 一个无参数函数,返回一个应当拥有以下两个属性的对象

  • done(boolean)

    • 如果迭代器可以产生序列中的下一个值,则为false。(这等价于没有指定 done这个属性。)

    • 如果迭代器已将序列迭代完毕,则为true。这种情况下,value是可选的,如果它依然存在,即为迭代结束之后的默认返回值。

  • value

    • 迭代器返回的任何JavaScript值。done为true 时可省略。

# 实现一个简单迭代器

const names = ["okarin", "retr0", "mayuri"]

let index = 0
const namesIterator = {
  next: function () {
    if (index < names.length) {
      return { done: false, value: names[index++] }
    } else {
      return { done: true, value: undefined }
    }
  },
}

for (let i = 0; i < 5; i++) {
  console.log(namesIterator.next())
}
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 迭代器函数

const names = ["okarin", "retr0", "mayuri"]

function arrIterator(arr) {
  let index = 0
  return {
    next: function () {
      if (index < arr.length) {
        return { done: false, value: arr[index++] }
      } else {
        return { done: true, value: undefined }
      }
    },
  }
}

const nameIterator = arrIterator(names)
console.log(nameIterator.next())
console.log(nameIterator.next())
console.log(nameIterator.next())
console.log(nameIterator.next())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 可迭代对象

但是上面的代码整体来说看起来是有点奇怪的:

  • 我们获取一个数组的时候,需要自己创建一个index变量,再创建一个所谓的迭代器对象
  • 事实上我们可以对上面的代码进行进一步的封装,让其变成一个可迭代对象;

什么又是可迭代对象呢?

  • 它和迭代器是不同的概念;
  • 当一个对象实现了iterable protocol协议时,它就是一个可迭代对象;
  • 这个对象的要求是必须实现@@iterator方法,在代码中我们使用Symbol.iterator 访问该属性;
const iterableObj = {
  names: ["abc", "cba", "nba"],
  [Symbol.iterator]: function () {
    let index = 0
    return {
      next: () => {
        if (index < this.names.length) {
          return { done: false, value: this.names[index++] }
        } else {
          return { done: true, value: undefined }
        }
      },
    }
  },
}

const iterator = iterableObj[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

const iterator1 = iterableObj[Symbol.iterator]()
console.log(iterator1.next())
console.log(iterator1.next())
console.log(iterator1.next())
console.log(iterator1.next())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

对象可以使用in操作符进行遍历,可以通过in操作符拿到对象的属性名。

const obj = {
    name:"okarin",
    age:18
}

for(let i in obj){
    console.log(i,obj[i])
}
//name okarin
//age 18
1
2
3
4
5
6
7
8
9
10

但是不能使用of操作符进行遍历,会提示:obj不是一个可迭代对象:


const obj = {
    name:"okarin",
    age:18
}

for(let i of obj){
    console.log(i)
}

//TypeError: obj is not iterable

1
2
3
4
5
6
7
8
9
10
11
12

对可迭代对象进行of遍历:

const iterableObj = {
  names: ["abc", "cba", "nba"],
  [Symbol.iterator]: function () {
    let index = 0
    return {
      next: () => {
        if (index < this.names.length) {
          return { done: false, value: this.names[index++] }
        } else {
          return { done: true, value: undefined }
        }
      },
    }
  },
}

for(let i of iterableObj){
    console.log(i)
}

//abc
//cba
//nba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

of操作符相当于一种next操作的语法糖,当得到的对象的 done属性为 false时,就把value赋值到 i 里面:

{ done: false, value: 'abc' }
{ done: false, value: 'cba' }
{ done: false, value: 'nba' }
{ done: true, value: undefined }
1
2
3
4

在done属性变为true时,就停止遍历。

# 原生迭代器对象

事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的:

String, Array, Map, Set, arguments对象、NodeList集合;

const names = ["okarin", "kurisu", "mayuri"]

const iterator = names[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

for (i of names) {
  console.log(i)
}

// { value: 'okarin', done: false }
// { value: 'kurisu', done: false }
// { value: 'mayuri', done: false }
// { value: undefined, done: true }
// okarin
// kurisu
// mayuri
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 可迭代对象的应用

那么这些东西可以被用在哪里呢?

  • JavaScript中语法:for...of、展开语法(spread syntax)、yield*(后面讲)、解构赋值(Destructuring_assignment);
  • 创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);
  • 一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);

# for of

const iterableObj = {
  names: ["okarin", "kurisu", "mayuri"],
  [Symbol.iterator]: function () {
    let index = 0
    return {
      next: () => {
        if (index < this.names.length) {
          return { done: false, value: this.names[index++] }
        } else {
          return { done: true, value: undefined }
        }
      },
    }
  },
}

for(let i of iterableObj){
    console.log(i)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# spread syntax

const iterableObj = {
    names: ["okarin", "kurisu", "mayuri"],
    [Symbol.iterator]: function () {
      let index = 0
      return {
        next: () => {
          if (index < this.names.length) {
            return { done: false, value: this.names[index++] }
          } else {
            return { done: true, value: undefined }
          }
        },
      }
    },
  }

  const newNames = [...iterableObj]
  console.log(newNames)

//[ 'okarin', 'kurisu', 'mayuri' ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

而对对象进行展开运算,不是通过迭代器实现的。ES8 (ES 2019)

const obj = { name: "okarin", age: 18 }
const newObj = { ...obj }
console.log(newObj)

//{ name: 'okarin', age: 18 }
1
2
3
4
5

# 解构语法

const [name1 ,name2] = iterableObj
console.log(name1,name2)
//okarin kurisu
1
2
3

而对对象进行解构,不是通过迭代器实现的。ES8 (ES 2019)

const obj = { name: "okarin", age: 18 }
const {name,age} = obj
console.log(name,age)

//okarin 18
1
2
3
4
5

# 创建一些其他对象时

const set = new Set(iterableObj)
const arr = Array.from(iterableObj)

console.log(set)
console.log(arr)

//Set { 'okarin', 'kurisu', 'mayuri' }
//[ 'okarin', 'kurisu', 'mayuri' ]
1
2
3
4
5
6
7
8

# Promise.all

Promise.all(iterableObj).then((res)=>{
    console.log(res)
})

//[ 'okarin', 'kurisu', 'mayuri' ]
1
2
3
4
5

# 自定义类的可迭代性

class Webset {
  constructor(name, web) {
    this.name = name
    this.web = web
  }
  getWebNum() {
    return this.web.length
  }

  //   *[Symbol.iterator]() {
  //     yield* this.web
  //   }
  [Symbol.iterator]() {
    let index = 0
    return {
      next: () => {
        if (index < this.web.length) {
          return { done: false, value: this.web[index++] }
        } else {
          return { done: true, value: undefined }
        }
      },
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 什么是生成器?

生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行暂停执行等。

平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。

  • 生成器函数也是一个函数,但是和普通的函数有一些区别: 首先,生成器函数需要在function的后面加一个符号 *

  • 其次,生成器函数可以通过yield关键字来控制函数的执行流程:

  • 最后,生成器函数的返回值是一个Generator(生成器):

生成器事实上是一种特殊的迭代器;

MDN : Instead, they return a special type of iterator, called a Generator.

function* foo() {
  console.log("函数开始执行~")
  const value1 = 100
  console.log(value1)
  yield
  const value2 = 200
  console.log(value2)
  yield
  const value3 = 300
  console.log(value3)
  yield
  console.log("函数执行结束~")
}

const generator = foo()
generator.next()
generator.next()
generator.next()
generator.next()

// 函数开始执行~
// 100
// 200
// 300
// 函数执行结束~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 生成器函数的执行流程

# next的返回值

const generator = foo()
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())


//函数开始执行~
//100
//{ value: undefined, done: false }
//200
//{ value: undefined, done: false }
//300
//{ value: undefined, done: false }
//函数执行结束~
//{ value: undefined, done: true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

当遇到yield会暂停执行,而return会终止执行

# 通过yield返回值

在yield后面添加值 可以返回

function* foo() {
  console.log("函数开始执行~")
  const value1 = 100
  console.log(value1)
  yield value1

  const value2 = 200
  console.log(value2)
  yield value2

  const value3 = 300
  console.log(value3)
  yield value3

  console.log("函数执行结束~")
  return "done"
}

const generator = foo()
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())


// 函数开始执行~
// 100
// { value: 100, done: false }
// 200
// { value: 200, done: false }
// 300
// { value: 300, done: false }
// 函数执行结束~
// { value: 'done', done: true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 生成器的其他方法使用

# 生成器上的next方法可以传递参数

function* foo() {
  console.log("函数开始执行~")

  const value1 = 100
  console.log(value1)
  const n = yield value1

  const value2 = 200 * n 
  console.log(value2)
  yield value2

  const value3 = 300
  console.log(value3)
  yield value3

  console.log("函数执行结束~")
  return "done"
}

const generator = foo()
console.log(generator.next())
console.log(generator.next(10))
console.log(generator.next())
console.log(generator.next())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 生成器的return终止执行

function* foo() {
  console.log("函数开始执行~")

  const value1 = 100
  console.log(value1)
  const n = yield value1

  const value2 = 200 * n 
  console.log(value2)
  yield value2

  const value3 = 300
  console.log(value3)
  yield value3

  console.log("函数执行结束~")
  return "done"
}

const generator = foo()
console.log(generator.next())
console.log(generator.return(10))
console.log(generator.next())
console.log(generator.next())

// 函数开始执行~
// 100
// { value: 100, done: false }
// { value: 10, done: true }
// { value: undefined, done: true }
// { value: undefined, done: true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 生成器的throw抛出异常

function* foo() {
  console.log("函数开始执行~")

  const value1 = 100
  try {
    yield value1
  } catch (error) {
    console.log("Error:", error)
  }

  const value2 = 200
  yield value2

  console.log("函数执行结束~")
}

const generator = foo()
console.log(generator.next())
console.log(generator.throw("error massage"))
console.log(generator.next())

// 函数开始执行~
// { value: 100, done: false }
// Error: error massage
// { value: 200, done: false }
// 函数执行结束~
// { value: undefined, done: true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 生成器替代迭代器使用

//迭代器
function createArrayIterator(arr) {
  let index = 0
  return {
    next: function () {
      if (index < arr.length) {
        return { done: false, value: arr[index++] }
      } else {
        return { done: true, value: undefined }
      }
    },
  }
}

const names = ["okarin","kurisu","mayuri"]
const arrayIterator = createArrayIterator(names)
console.log(arrayIterator.next())
console.log(arrayIterator.next())
console.log(arrayIterator.next())
console.log(arrayIterator.next())

// { value: 'okarin', done: false }
// { value: 'kurisu', done: false }
// { value: 'mayuri', done: false }
// { value: undefined, done: true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//生成器
function* createArrayIterator(arr) {
    for (let item of arr) {
      yield item
    }
  }
  
  const names = ["okarin", "kurisu", "mayuri"]
  const arrayIterator = createArrayIterator(names)
  console.log(arrayIterator.next())
  console.log(arrayIterator.next())
  console.log(arrayIterator.next())
  console.log(arrayIterator.next())

// { value: 'okarin', done: false }
// { value: 'kurisu', done: false }
// { value: 'mayuri', done: false }
// { value: undefined, done: true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# yield*

相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值;

function* createArrayIterator(arr) {
  yield* arr
}

const names = ["okarin", "kurisu", "mayuri"]
const arrayIterator = createArrayIterator(names)
console.log(arrayIterator.next())
console.log(arrayIterator.next())
console.log(arrayIterator.next())
console.log(arrayIterator.next())

// { value: 'okarin', done: false }
// { value: 'kurisu', done: false }
// { value: 'mayuri', done: false }
// { value: undefined, done: true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

对某个范围迭代

function* createRangeIterator(start, end) {
  let index = start
  while (index < end) {
    yield index++
  }
}

const rangeIterator = createRangeIterator(1, 5)
for (let i = 0; i < 5; i++) {
  console.log(rangeIterator.next())
}


// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 4, done: false }
// { value: undefined, done: true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Last Updated: 2022/3/16上午1:03:34