2

📜 名词解释

🎈 迭代

在软件开发领域,“迭代”的意思是按照顺序反复多次执行一段程序。

🎈 可迭代对象(iterable)

可迭代对象就是指一个包含可以在其值上迭代的迭代器的对象。

从一个可迭代对象中提取迭代器的方法是:可迭代对象必须支持一个函数,其名称是专门的ES6符号值Symbol.iterator。调用这个函数时,它会返回一个迭代器。

比如,下面代码中的a就是可迭代对象:

const a = [1,3,5,6]
const it = a[Symbol.iterator]()

it.next().value // 1
it.next().value // 3
it.next().value // 5
it.next().value // 6
it.next().value // undefined

🧮 迭代器

🤓 什么是迭代器

如果一个对象有next()方法,调用此方法会返回具有value(任意类型)和done(布尔类型)的结果对象,那它就是个迭代器对象。

function createIterator(limit) {
  let value = 1

  return {
    next() {
      if (value < limit) {
        return {
          value: value++,
          done: false,
        }
      } else {
        return {
          value: undefined,
          done: true,
        }
      }
    },
  }
}

const it = createIterator(3)

it.next() // { value: 1, done: false }
it.next() // { value: 2, done: false }
it.next() // { value: undefined, done: true }
it.next() // { value: undefined, done: true }

🤔 为什么会有迭代器

下面的代码是个简单的迭代操作:

const arr = ['a', 'b', 'c', 'd']

for (let i = 0, len = arr.length; i < len; i++) {
  console.log(arr[i])
}

上面的代码通过变量i来跟踪arr数组的索引,虽然简单,但如果将多个循环嵌套则需要追踪多个变量,代码的复杂度会大大增加。迭代器的出现旨在消除这种复杂性并减少循环中的错误

🤯 你应该知道的

👣 迭代器的工作流程

每次调用迭代器的next()方法,都返回一个结果对象,此结果对象有两个属性:一个是value,表示下一个将要返回的值;另一个是done,是一个布尔类型的值,当没有可返回的数据时,其值为true,否则为false

如果在最后一个值返回后,再调用next()方法,结果对象的value属性值就不是数据集的一部分,跟函数类似,如果没有明确指定,则默认返回undefined

🦈 迭代器和可迭代对象是分离的

迭代器就像个指针对象,它的next()方法用于移动指针。

迭代器和可迭代对象是分开的,执行迭代器的next()方法,其实是去可迭代对象中找数据,如果可迭代对象在迭代期间被修改了,那么迭代器也会反映相应的变化。

let arr = ['foo', 'baz']
const it = arr[Symbol.iterator]()

it.next() // { value: "foo", done: false }

// 在数组中间插入值
arr.splice(1, 0, 'bar')

it.next() // { value: "bar", done: false }
it.next() // { value: "baz", done: false }
it.next() //  { value: undefined, done: true }
it.next() //  { value: undefined, done: true }

⛔ 若迭代提前关闭,要如何处理

可选的return()方法用于指定在迭代器提前关闭时执行的逻辑。

提前关闭可能的情况包括:

  • for-of循环通过breakcontinuereturnthrow提前退出。
class Test {
  limit = 1

  constructor(limit) {
    this.limit = limit
  }

  [Symbol.iterator]() {
    let i = 1
    let limit = this.limit

    return {
      next() {
        let done = i < limit ? false : true
        
        return { value: i++, done }
      },
      return() {
        console.log('提前终止喽')

        return { done: true }
      },
    }
  }
}

const t = new Test(5)

for (let item of t) {
  if (item === 3) {
    break
  }

  console.log(item)
}

// 1
// 2
// 提前终止喽
  • 解构操作并未消费所有值。

🐎 用生成器可以让创建迭代器的过程变简单

通过前面的示例代码可以看出迭代器的编写规则是较复杂的。

ES6中引入了生成器对象,它可以让创建迭代器的过程变得简单。因为生成器就是一种返回迭代器的函数,它的调用方式与普通函数相同,只不过返回的是个迭代器。

function* createIterator(limit) {
  for (let i = 1; i < limit; i++) {
    yield i
  }
}

const it = createIterator(3)

console.log(it.next().value) // 1
console.log(it.next().value) // 2
console.log(it.next().value) // undefined

📦 内建迭代器

可迭代对象具有Symbol.iterator属性,通过该属性就可以获取到对象的默认迭代器。

const values = [1, 2, 3]
const it = values[Symbol.iterator]()

it.next() // { value: 1, done: false }
it.next() // { value: 2, done: false }
it.next() // { value: 3, done: false }
it.next() // { value: undefined, done: true }

那要这么说的话,有默认的迭代器,对应的就得有非默认的迭代器。事实还真是这样,在ES6中已经为许多内建类型提供了内建迭代器。

集合迭代器

ES6中有三种集合对象:数组、SetMap。这三种对象都内建了三种迭代器。

  • entries() 返回一个迭代器,其值为多个键值对。
const arr = ['red', 'green', 'blue']
const set = new Set([123, 456, 789])
let map = new Map()

map.set('name', '张三')
map.set('age', 18)

// 数组返回值的第一个元素是数字型索引
for (let entry of arr.entries()) {
  console.log(entry) // [ 0, "red" ], [ 1, "green" ], [ 2, "blue" ]
}

// Set集合中的值被同时作为键和值使用
for (let entry of set.entries()) {
  console.log(entry) // [ 123, 123 ], [ 456, 456 ], [ 789, 789 ]
}

for (let entry of map.entries()) {
  console.log(entry) // [ "name", "张三" ], [ "age", 18 ]
}

注意:数组返回值的第一个元素是数字型索引,Set集合中的值被同时作为键和值使用

  • values() 返回一个迭代器,其值为集合的值。
const arr = ['red', 'green', 'blue']
const set = new Set([123, 456, 789])
let map = new Map()

map.set('name', '张三')
map.set('age', 18)

for (let value of arr.values()) {
  console.log(value) // "red", "green", "blue"
}

for (let value of set.values()) {
  console.log(value) // 123, 456, 789
}

for (let value of map.values()) {
  console.log(value) // "张三", 18
}
  • keys() 返回一个迭代器,其值为集合中的所有键名。
const arr = ['red', 'green', 'blue']
const set = new Set([123, 456, 789])
let map = new Map()

map.set('name', '张三')
map.set('age', 18)

for (let key of arr.keys()) {
  console.log(key) // 0, 1, 2
}

for (let key of set.keys()) {
  console.log(key) // 123, 456, 789
}

for (let key of map.keys()) {
  console.log(key) // "name", "age"
}

重点来了:数组和Set集合的默认迭代器是values()Map集合的默认迭代器是entries()

字符串迭代器

自ES5发布后,字符串越来越像数组啦,比如,可以通过方括号访问字符串中的字符。

const str = 'hello'

console.log(str[0]) // "h"
console.log(str[1]) // "e"

可以使用for-of语句迭代字符串的每个字符。

const str = 'hello'

for (const item of str) {
  console.log(item) // "h", "e", "l", "l", "o"
}
NodeList迭代器

DOM定义中的NodeList类型(定义在HTML标准而不是ES6标准中),也有默认迭代器,其与数组的默认迭代器完全一致。

<p id="p1">张三</p>
<p id="p2">李四</p>
<p id="p3">王五</p>

<script>
  const nodes = document.querySelectorAll('p')

  for (const item of nodes) {
    console.log(item.id) // p1, p2, p3
  }
</script>

BigDipper
17 声望0 粉丝