《javascript高级程序设计》学习笔记 | 7.2.迭代器模式

小讴
关注前端小讴,阅读更多原创技术文章

迭代器模式

  • ES6 新增了 2 个高级特性:迭代器生成器
迭代方法优/缺点
for① 需知道如何使用数据结构 ② 遍历顺序不是固有的
forEach()① 无法标识迭代终止 ② 只适用数组 ③ 回调结构较笨拙
Iterator接口无需了解可迭代对象的结构,只需知道如何取得连续的值

相关代码 →

可迭代协议

  • 实现 Iterable 接口要求同时具备:① 支持迭代的自我识别 ② 创建实现 Iterator 接口的对象
  • 必须暴露一个属性作为默认迭代器,属性使用Symbol.iterator作为键
  • 默认迭代属性必须引用一个迭代器工厂函数,调用工厂函数返回一个新迭代器
  • 实现了 Iterable 接口的内置类型:字符串、数组、映射、集合、arguments 对象、NodeList 等 DOM 集合类型
// 未实现迭代器工厂函数
let num = 1
let obj = {}
console.log(num[Symbol.iterator]) // undefined
console.log(obj[Symbol.iterator]) // undefined

// 实现了迭代器工厂函数
let str = 'abc'
let arr = ['a', 'b', 'c']
let map = new Map().set('a', 1).set('b', 2)
let set = new Set().add('a').add('b')
console.log(str[Symbol.iterator]) // ƒ [Symbol.iterator]() { [native code] }
console.log(arr[Symbol.iterator]) // ƒ values() { [native code] }
console.log(map[Symbol.iterator]) // ƒ entries() { [native code] }
console.log(set[Symbol.iterator]) // ƒ values() { [native code] }

// 调用迭代器工厂函数,生成新的迭代器
console.log(str[Symbol.iterator]()) // StringIterator {}
console.log(arr[Symbol.iterator]()) // ArrayIterator {}
console.log(map[Symbol.iterator]()) // MapIterator { "a" => 1, "b" => 2 }
console.log(set[Symbol.iterator]()) // StringIterator { "a", "b" }
  • 不需要显示调用迭代器工厂函数,实现可迭代协议的所有类型自动兼容接受可迭代对象的任何语言特性
  • 接受可迭代对象的原生语言特性包括:

    • for-of循环
    • 数组解构
    • 扩展操作符
    • Array.from()
    • 创建集合
    • 创建映射
    • Promise.all()接收由期约组成的可迭代对象
    • Promise.race()接收由期约组成的可迭代对象
    • yield*操作符,在生成器中使用
for (let el of arr) {
  console.log(el) // for-of循环
  /* 
    'a'
    'b'
    'c'
   */
}

let [a, b, c] = arr // 数组解构
console.log(a, b, c) // 'a' 'b' 'c'

let arr2 = [...arr] // 扩展操作符
console.log(arr2) // [ 'a', 'b', 'c' ]

let arr3 = Array.from(arr) // Array.from()
console.log(arr3) // [ 'a', 'b', 'c' ]

let set2 = new Set(arr)
console.log(set2) // Set(3) { 'a', 'b', 'c' }

let pairs = arr.map((x, i) => [x, i])
console.log(pairs) // [ [ 'a', 0 ], [ 'b', 1 ], [ 'c', 2 ] ]
let map2 = new Map(pairs)
console.log(map2) // Map(3) { 'a' => 0, 'b' => 1, 'c' => 2 }
  • 如果对象原型链上的父类实现了 Iterabe 接口,该对象也实现了这个接口
class FooArray extends Array {}
let fooArr = new FooArray('foo', 'bar', 'baz')

for (let el of fooArr) {
  console.log(el)
  /* 
    foo
    bar
    baz
   */
}

迭代器协议

  • 迭代器 API 使用next()方法在可迭代对象中遍历数据,每次成功调用next()都返回一个IteratorResult对象:

    • IteratorResult包含 2 个属性donevalue
    • done 是一个布尔值,false 为未耗尽,true 为耗尽
    • value包含可迭代对象的下一个值(若 done 为 true 则 value 为 undefined)
    • 迭代器到达done:true状态,后续再调用next()一直返回{done:true,value:undefined}
let arr4 = ['foo', 'bar'] // 可迭代对象
console.log(arr4[Symbol.iterator]) // ƒ values() { [native code] },迭代器工厂函数

let iter = arr4[Symbol.iterator]() // 迭代器
console.log(iter) // ArrayIterator {}

console.log(iter.next()) // { value: 'foo', done: false },执行迭代
console.log(iter.next()) // { value: 'foo', done: false },执行迭代
console.log(iter.next()) // { value: undefined, done: true },执行迭代
console.log(iter.next()) // { value: undefined, done: true },执行迭代
  • 同一个可迭代对象的不同迭代器实例之间没有联系,每个迭代器都独立地遍历可迭代对象
let iter2 = arr4[Symbol.iterator]() // 迭代器iter2,迭代可迭代对象arr4
let iter3 = arr4[Symbol.iterator]() // 迭代器iter2,迭代可迭代对象arr4

console.log(iter2.next()) // { value: 'foo', done: false }
console.log(iter3.next()) // { value: 'foo', done: false }
console.log(iter3.next()) // { value: 'bar', done: false }
console.log(iter2.next()) // { value: 'bar', done: false }
  • 可迭代对象在迭代期间被修改,迭代器反映相应变化;迭代器维护的是指向可迭代对象的引用,会阻止垃圾回收程序回收可迭代对象
let arr5 = ['foo', 'baz']
let iter4 = arr5[Symbol.iterator]()
console.log(iter4.next()) // { value: 'foo', done: false }

arr5.splice(1, 0, 'bar') // 迭代期间,可迭代对象被修改
console.log(iter4.next()) // { value: 'bar', done: false }
console.log(iter4.next()) // { value: 'baz', done: false }
console.log(iter4.next()) // { value: undefined, done: true }

自定义迭代器

  • 任何实现了 Iterable 接口(具有 Symbol.iterator 属性)的对象都可以作为迭代器使用
class Counter {
  // 构造函数
  constructor(limit) {
    this.count = 1
    this.limit = limit
  }
  // Iterable 接口,实现自定义迭代
  [Symbol.iterator]() {
    return this
  }
  // 原型上的迭代方法
  next() {
    if (this.count <= this.limit) {
      return { value: this.count++, done: false }
    } else {
      return { value: undefined, done: true }
    }
  }
}

let counter = new Counter(5)
console.log(counter) // Counter { count: 1, limit: 5 },构造函数
console.log(counter[Symbol.iterator]) // ƒ [Symbol.iterator]() { return this },迭代器工厂函数

for (let i of counter) {
  console.log(i)
  /* 实现了自定义迭代器:
    1
    2
    3
    4
    5 
  */
}

for (let i of counter) {
  console.log(i)
  /* 只能迭代1次
    nothing logged
  */
}
  • 将计数器变量放到闭包里,实现同一个可迭代对象能够创建多个迭代器
class Counter2 {
  constructor(limit) {
    this.limit = limit
  }
  [Symbol.iterator]() {
    let count = 1 // 计数器变量放到闭包中
    let limit = this.limit
    return {
      next() {
        if (count <= limit) {
          return { value: count++, done: false }
        } else {
          return { value: undefined, done: true }
        }
      },
    }
  }
}
let counter2 = new Counter2(3)

for (let i of counter2) {
  console.log(i)
  /* 
    1
    2
    3
  */
}

for (let i of counter2) {
  console.log(i)
  /* 同一个可迭代对象,能够创建多个迭代器:
    1
    2
    3
  */
}
  • 任何迭代器,调用 Symbol.iterator 属性引用的工厂函数后,返回与原迭代器相同的迭代器
let arr6 = ['foo', 'bar', 'baz']
let iter5 = arr6[Symbol.iterator]()
console.log(iter5) // ArrayIterator {}

let iter6 = iter5[Symbol.iterator]() // 迭代器再次调用工厂函数,生成新的迭代器
console.log(iter6) // // ArrayIterator {}
console.log(iter5 === iter6) // true

let iter7 = iter6[Symbol.iterator]() // 迭代器再次调用工厂函数,生成新的迭代器
console.log(iter7) // // ArrayIterator {}
console.log(iter6 === iter7) // true

for (let i of iter7) {
  console.log(i)
  /* 
    'foo'
    'bar'
    'baz'
  */
}

提前终止迭代器

  • 可选的return()方法可在迭代器未耗尽时,指定迭代器提前关闭时执行的逻辑,迭代器提前关闭的可能情况包括:

    • for-of循环通过breakcontinuereturnthrow提前退出
    • 解构操作未消费所有值
class Counter3 {
  constructor(limit) {
    this.limit = limit
  }
  [Symbol.iterator]() {
    let count = 1 // 计数器变量放到闭包中
    let limit = this.limit
    return {
      next() {
        if (count <= limit) {
          return { value: count++, done: false }
        } else {
          return { value: undefined, done: true }
        }
      },
      // 提前终止迭代器的方法
      return() {
        console.log('Exiting early')
        return { done: true }
      },
    }
  }
}

let counter3 = new Counter3(5)

for (let i of counter3) {
  if (i > 2) {
    break // 提前终止迭代器,调用迭代器的return()方法
  }
  console.log(i)
  /* 
    1
    2
    'Exiting early'
  */
}

try {
  for (let i of counter3) {
    if (i > 2) {
      throw 'err' // 提前终止迭代器,调用迭代器的return()方法
    }
    console.log(i)
    /* 
    1
    2
    'Exiting early'
  */
  }
} catch (e) {}

let [a2, b2, c2, d2, e2, f2] = counter3 // 解构操作,消费所有值
console.log(a2, b2, c2, d2, e2, f2) // 1 2 3 4 5 undefined
let [a3, b3, c3] = counter3 // 'Exiting early',解构操作,未消费所有值
  • 若迭代器未关闭,提前终止后可以从上次离开的地方继续迭代,数组的迭代器就是不能关闭的
let arr7 = [1, 2, 3, 4, 5]
let iter8 = arr7[Symbol.iterator]()

for (let i of iter8) {
  console.log(i)
  if (i > 2) {
    break // 提前退出迭代器,但不关闭
  }
  /* 
    1
    2
    3
 */
}

for (let i of iter8) {
  console.log(i)
  /* 继续迭代
    4
    5
 */
}
  • 迭代器实例的return属性是否为函数对象,决定迭代器是否可关闭

    • 给不可关闭的迭代器增加return()方法不能让该迭代器变得可关闭,但提前终止会调用新增的return()方法
let arr8 = [1, 2, 3, 4, 5]
let iter9 = arr8[Symbol.iterator]()

console.log(iter9.return) // undefined,迭代器不可关闭

iter9.return = function () {
  // 追加return方法,但无法让迭代器变得可关闭
  console.log('Exiting early')
  return { done: true }
}

for (let i of iter9) {
  console.log(i)
  if (i > 2) {
    break // 提前退出迭代器
  }
  /* 
    1
    2
    3
    'Exiting early',提前终止迭代器仍会调用return()方法
 */
}

for (let i of iter9) {
  console.log(i)
  /* 继续迭代
    4
    5
 */
}

总结 & 问点

  • 实现迭代有哪些方式?其优、缺点分别是什么?默认迭代器必须使用什么属性作为键?
  • 如何检测数据类型是否实现了迭代器工厂函数?调用这个工厂函数会生成什么?
  • 迭代器 API 调用哪个方法遍历数据?其返回值是什么?同一个可迭代对象的不同迭代器实例之间有联系么?
  • 迭代器真正维护的是什么?其对垃圾回收程序有什么影响?可迭代对象在迭代期间被修改,迭代器受影响么?
  • 写一段代码创建一个自定义迭代器,实现“同一个可迭代对象能够创建多个迭代器”
  • 迭代器调用 Symbol.iterator 属性引用的工厂函数会生成什么?
  • 如何指定迭代器提前关闭时执行的逻辑?哪些情况下迭代器会提前终止?
  • 提前终止的迭代器若未关闭,是否可继续迭代?如何判断迭代器是否可关闭?
  • 不可关闭的迭代器能否变成可关闭的?请用代码证实
阅读 106

《javascript高级程序设计(第四版)》学习笔记
重学《javascript高级程序设计》,耐心学习,核心知识永不过时

从前做策划、半路出家的前端程序猿,爱吉他爱户外爱篮球更爱写代码,努力为了更好的生活。大家可添加我微信(powerful_simon),互相交流互相学习,一起弹琴唱歌打球爬山。同时还望前辈们多多指点,多多帮忙内推~

173 声望
7 粉丝
0 条评论
你知道吗?

从前做策划、半路出家的前端程序猿,爱吉他爱户外爱篮球更爱写代码,努力为了更好的生活。大家可添加我微信(powerful_simon),互相交流互相学习,一起弹琴唱歌打球爬山。同时还望前辈们多多指点,多多帮忙内推~

173 声望
7 粉丝
宣传栏