ChuChencheng / note

菜鸡零碎知识笔记
Creative Commons Zero v1.0 Universal
3 stars 0 forks source link

ES6 Iterator #27

Open ChuChencheng opened 4 years ago

ChuChencheng commented 4 years ago

概念

遍历器(Iterator),一种为不同数据结构提供统一的访问机制的接口。

作用:

  1. 提供统一、简便的访问接口
  2. 使数据结构的成员能够按某种次序排列
  3. for...of 遍历命令使用

规格描述

TypeScript 定义:

interface Iterable<T> {
    [Symbol.iterator](): Iterator<T>;
}

interface Iterator<T, TReturn = any, TNext = undefined> {
    // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
    next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
    return?(value?: TReturn): IteratorResult<T, TReturn>;
    throw?(e?: any): IteratorResult<T, TReturn>;
}

type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;

interface IteratorYieldResult<TYield> {
    done?: false;
    value: TYield;
}

interface IteratorReturnResult<TReturn> {
    done: true;
    value: TReturn;
}

只要有 [Symbol.iterator] 属性,就认为是“可遍历的”(iterable)

for...of 循环中,会调用 [Symbol.iterator] ,并执行其返回的 next 函数:

const iterable = {
  [Symbol.iterator] () {
    let i = 0
    return {
      next () {
        return { value: i++, done: i > 5 }
      }
    }
  }
}

for (let i of iterable) {
  console.log(i)
}
// 0 1 2 3 4

const iter = iterable[Symbol.iterator]()

console.log(iter.next()) // { value: 0, done: false }
console.log(iter.next()) // { value: 1, done: false }
console.log(iter.next()) // { value: 2, done: false }
console.log(iter.next()) // { value: 3, done: false }
console.log(iter.next()) // { value: 4, done: false }
console.log(iter.next()) // { value: 5, done: true }

原生具备 Iterator 接口的数据结构:

原生数组 Iterator 接口示例:

const arr = [1, 2, 3]
const it = arr[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 }

调用 Iterator 接口的场合

  1. 解构赋值
  2. 扩展运算符(...)
  3. yield*
  4. 任何接受数组作为参数的场合,例如 for...of, Array.from(), Map(), Set(), WeakMap(), WeakSet(), Promise.all(), Promise.race(), Promise.allSettled()

Iterator 接口与 Generator 函数

见 Generator 笔记

return 与 throw

遍历器对象除了 next ,还可以有 returnthrow 方法。这两个方法是可选的。

return 返回一个对象,在 for...of 循环提前退出(抛出错误或者手动 break )时调用,可作为清理或释放资源用。

例如:

function readLinesSync (file) {
  return {
    [Symbol.iterator] () {
      return {
        next () {
          return { done: false }
        },
        return () {
          file.close()
          return { done: true }
        }
      }
    }
  }
}

for (let line of readLinesSync(fileName)) {
  console.log(line)
  break
}

// 或者

for (let line of readLinesSync(fileName)) {
  console.log(line)
  throw new Error()
}

定义一个逐行读取文件的函数,在遍历完第一行后,如果是 break 或抛出错误了,则执行 return 方法,关闭文件。如果是抛出错误,会在 return 执行之后再抛出(个人未验证)

throw 方法主要配合 Generator 函数使用,见 Generator 笔记

for...offor...in

for...of 循环作为遍历所有数据结构的统一的方法,只要部署了 Symbol.iterator 属性,就可以用 for...of 来遍历,因此,普通的对象不能用 for...of 遍历,只能使用 for...in ,而数组、Map、Set、arguments 对象等,就可以用 for...of 来遍历。

for...in 的缺点:

  1. 数组键名是数字,但遍历的时候是以字符串 '0', '1' 等作为键名
  2. for...in 可能会遍历到原型链上的键
  3. for...in 不保证遍历的顺序
for (let key in [1, 2, 3]) {
  console.log(typeof key) // string
}
const arr = [1, 2, 3]
Object.setPrototypeOf(arr, { protoProperty: 666 })
for (let key in arr) {
  console.log(key)
}
// 0 1 2 protoProperty

总之 for...in 主要是为遍历对象而设计的,不适用于数组

另外提一下 forEach 这种函数遍历的方式,优点是简洁方便,但缺点也很明显:无法中途跳出循环

参考

http://es6.ruanyifeng.com/#docs/iterator