前言

  前文再续,书接上一回。上周一个偶然的机会写的一篇this指向,超出了我的意外,获得了许多的赞和保藏,所以我决议从现在开始,坚持每周用心写一篇有关于前端的技术文章(ES6和难理解的常识点)。一边用来稳固自己以前学习的常识,一边分享给咱们,让咱们也能有所收成。别的今天是中秋节,祝咱们中秋节高兴,中秋节要吃月饼。

1. 什么是迭代器?

  概念(维基百科): 迭代器(iterator),是确使用户可在容器目标(container,例如链表或数组)上遍访的目标[1][2][3],设计人员使用此接口无需关心容器目标的内存分配的完结细节。
JS中的迭代器

  • 其实质便是一个目标,契合迭代器协议(iterator protocol)
  • 迭代器协议
    1. 其目标回来一个next函数
    2. 调用next函数回来一个目标,其目标中包含两个特点
      • done(完结),它的值为布尔类型,也便是true/false

        • 假设这个迭代器没有迭代完结即回来{done:false}
        • 当这个迭代器完结了即回来{done:true}
      • value(值),它能够回来js中的任何值,TS中表示可为:value:any类型

1.1 迭代器的根本完结

考虑以下代码:

let index = 0
const bears = ['ice', 'panda', 'grizzly']
let iterator = {
  next() {
    if (index < bears.length) {
      return { done: false, value: bears[index++] }
    }
    return { done: true, value: undefined }
  }
}
console.log(iterator.next()) //{ done: false, value: 'ice' }
console.log(iterator.next()) //{ done: false, value: 'panda' }
console.log(iterator.next()) //{ done: false, value: 'grizzly' }
console.log(iterator.next()) //{ done: true, value: undefined }
  1. 是一个目标,完结了next办法,next办法回来了一个目标,有done特点和value特点,且key的值类型也为booleanany,契合迭代器协议,是一个妥妥的迭代器没跑了。
  2. 坏处
    • 违背了高内聚思维,明明indexiterator目标是属于一个整体,我却使用了全局变量,从V8引擎的GC,可达性(也便是标记铲除)来看,假设bears = null ,不手动设置为null很有或许会形成内存走漏,而且内聚性低。
    • 假设我要创建一百个迭代器目标呢? 那我就自己界说一百遍吗?肯定过错的,咱们要把它封装起来,这样内聚性又高,又能进行复用,一箭双雕,一举两得,真的是very beautiful,very 高雅。

1.2 迭代器的封装完结

考虑一下代码:

const bears = ['ice', 'panda', 'grizzly']
function createArrIterator(arr) {
  let index = 0
  let _iterator = {
    next() {
      if (index < arr.length) {
        return { done: false, value: arr[index++] }
      }
      return { done: true, value: undefined }
    }
  }
  return _iterator
}
let iter = createArrIterator(bears)
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
  • 内聚性非常高,尽最大或许进行了复用,削减冗余代码

2. 什么是可迭代目标

迭代器目标和可迭代目标是一个不同的东西,尽管它们存在关联,而且面试的时分经常面这些概念,废话不多说,咱们直接进入主题。

  • 首先便是一个目标,且契合可迭代目标协议(iterable protocol)
  • 可迭代目标协议
    1. 完结了[Symbol.iterator]为key的办法,且这个办法回来了一个迭代器目标
  • 绕了一大圈总算把概念搞明白了,那可迭代目标有什么优点呢? 有什么应用场景呢?
    1. for of 的时分,其实质便是调用的这个函数,也便是[Symbol.iterator]为key的办法

2.1 原生可迭代目标(JS内置)

  1. String
  2. Array
  3. Set
  4. NodeList 类数组目标
  5. Arguments 类数组目标
  6. Map

2.1.1 部分for of 演示

let str = 'The Three Bears'
const bears = ['ice', 'panda', 'grizzly']
for( let text of str) {
  console.log(text) //字符串每个遍历打印
}
for( let bear of bears) {
  console.log(bear)
}
 //ice panda grizzly

2.1.2 查看内置的[Symbol.iterator]办法

  • 上面给咱们举例了许多可迭代目标,那它们必定是契合可迭代目标协议的,考虑以下代码
const bears = ['ice', 'panda', 'grizzly']
//数组的Symbol.iterator办法
const iter = bears[Symbol.iterator]()
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
const nickName = 'ice'
//字符串的Symbol.iterator办法
const strIter = nickName[Symbol.iterator]()
console.log(strIter.next())
console.log(strIter.next())
console.log(strIter.next())
console.log(strIter.next())

2.2 可迭代目标的完结

let info = {
  bears: ['ice', 'panda', 'grizzly'],
  [Symbol.iterator]: function() {
    let index = 0
    let _iterator = {
       //这儿一定要箭头函数,或许手动保存上层效果域的this
       next: () => {
        if (index < this.bears.length) {
          return { done: false, value: this.bears[index++] }
        }
        return { done: true, value: undefined }
      }
    }
    return _iterator
  }
}
let iter = info[Symbol.iterator]()
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
//契合可迭代目标协议 就能够使用 for of 遍历
for (let bear of info) {
  console.log(bear)
}
//ice panda grizzly
  • 契合可迭代目标协议,是一个目标,有[Symbol.iterator]办法,而且这个办法回来了一个迭代器目标。
  • 当我使用for of 遍历,就会自动的调用这个办法。

2.3 可迭代目标的应用

  • for of
  • 打开语法
  • 解构语法
  • promise.all(iterable)
  • promise.race(iterable)
  • Array.from(iterable)

2.4 自界说类迭代完结

class myInfo {
  constructor(name, age, friends) {
    this.name = name
    this.age = age
    this.friends = friends
  }
  [Symbol.iterator]() {
    let index = 0
    let _iterator = {
      next: () => {
        const friends = this.friends
        if (index < friends.length) {
          return {done: false, value: friends[index++]}
        }
        return {done: true, value: undefined}
      }
    }
    return _iterator
  }
}
const info = new myInfo('ice', 22, ['panda','grizzly'])
for (let bear of info) {
  console.log(bear)
}
//panda
//grizzly
  • 此事例仅仅简略的对friends进行了迭代,你也能够迭代你想要的全部东西…
  • 记住此事例,后续咱们会对这个事例进行重构,高雅的会让你不能用言语来形容。

3. 生成器函数

生成器是ES6新增的一种能够对函数操控的方案,能灵敏的操控函数的暂停履行,持续履行等。

生成器函数和普通函数的不同

  • 界说: 普通函数function界说,生成器函数function*,要在后面加*
  • 生成器函数能够经过 yield 来操控函数的履行
  • 生成器函数回来一个生成器(generator),生成器是一个特别的迭代器

3.1 生成器函数根本完结

function* bar() {
  console.log('fn run')
}
bar()
  • 咱们会发现,这个函数竟然没有履行。咱们前面说过,它是一个生成器函数,它的回来值是一个生成器,同时也是一个特别的迭代器,所以跟普通函数比较,好像暂停了,那怎么让他履行呢?接下来咱们进一步探讨。

3.2 生成器函数单次履行

function* bar() {
  console.log('fn run')
}
const generator = bar()
console.log(generator.next())
//fn run
//{ value: undefined, done: true }
  • 回来了一个生成器,咱们调用next办法就能够让函数履行,而且next办法是有回来值的,咱们上面讲迭代器的时分有探讨过,而value没有回来值那便是undefined。那上面说的yield关键字在哪,到底是怎么操控函数的呢?是怎么用的呢?

3.3 生成器函数屡次履行

function* bar() {
  console.log('fn run start')
  yield 100
  console.log('fn run...')
  yield 200
  console.log('fn run end')
  return 300
}
const generator = bar()
//1. 履行到第一个yield,暂停之后,而且把yield的回来值 传入到value中
console.log(generator.next())
//2. 履行到第一个yield,暂停之后,而且把yield的回来值 传入到value中
console.log(generator.next())
//3. 履行剩下代码
console.log(generator.next())
//打印结果:
//fn run start
//{done:false, value: 100}
//fn run...
//{done:false, value: 200}
//fn run end
//{done:true, value: 300}
  • 现在咱们恍然大悟,每当调用next办法的时分,代码就会开始履行,履行到yield x,后就会暂停,等候下一次调用next持续往下履行,周而复始,没有了yield关键字,进行最终一次next调用回来done:true

3.4 生成器函数的分段传参

我有一个需求,已然生成器能操控函数分段履行,我要你完结一个分段传参。
考虑以下代码:


function* bar(nickName) {
  const str1 = yield nickName
  const str2 = yield str1 + nickName
  return str2 + str1 + nickName
}
const generator = bar('ice')
console.log(generator.next())
console.log(generator.next('panda '))
console.log(generator.next('grizzly '))
console.log(generator.next())
// { value: 'ice', done: false }
// { value: 'panda ice', done: false }
// { value: 'grizzly panda ice', done: true }
// { value: undefined, done: true }
  • 假设没有接触过这样的代码会比较古怪
    • 当我调用next函数的时分,yield的左边是能够承受参数的,也并不是一切的next办法的实参都能传递到生成器函数内部
    • yield左边接纳的,是第2次调用next传入的实参,那第一次传入的就没有yield关键字接纳,一切只有当我调用bar函数的时分传入。
    • 最终一次next调用,传入的参数我也调用不了,因为没有yield关键字能够接纳了。
  • 许多开发者会疑惑,这样写有什么用呢? 可读性还差,但是在处理异步数据的时分就非常有用了,后续会在promise中文章中介绍。

3.5 生成器替代迭代器

前面咱们讲到,生成器是一个特别的迭代器,那生成器必定是能够替代迭代器目标的,考虑以下代码。

let bears = ['ice','panda','grizzly']
function* createArrIterator(bears) {
  for (let bear of bears) {
    yield bear
  }
}
const generator = createArrIterator(bears)
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

其实这儿还有一种语法糖的写法yield*

  • yield* 顺次迭代这个可迭代目标,相当于遍历拿出每一项 yield item(伪代码)

考虑以下代码:

let bears = ['ice','panda','grizzly']
function* createArrIterator(bears) {
  yield* bears
}
const generator = createArrIterator(bears)
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
  • 顺次迭代这个可迭代目标,回来每个item值

4. 可迭代目标的终极封装

class myInfo {
  constructor(name, age, friends) {
    this.name = name
    this.age = age
    this.friends = friends
  }
  *[Symbol.iterator]() {
    yield* this.friends
  }
}
const info = new myInfo('ice', 22, ['panda','grizzly'])
for (let bear of info) {
  console.log(bear)
}
//panda
//grizzly
  • 回忆以下可迭代目标协议
    • 是一个目标而且有[Symbol.iterator]办法
    • 这个办法回来一个迭代器目标 生成器函数回来一个生成器,是一个特别的迭代器

5. 总结

5.1 迭代器目标

  1. 实质便是一个目标,要契合迭代器协议
  2. 有自己对应的next办法,next办规律回来一组数据{done:boolean, value:any}

5.2 可迭代目标

  1. 实质便是目标,要契合可迭代目标协议
  2. [Symbol.iterator]办法,而且调用这个办法回来一个迭代器

5.3 生成器函数

  1. 能够操控函数的暂停履行和持续履行
  2. 经过function* bar() {} 这种方式界说
  3. 不会立马履行,而是回来一个生成器,生成器是一个特别的迭代器目标
  4. yield 关键字能够操控函数分段履行
  5. 调用回来生成器的next办法进行履行

6. 结语

  • 坚持自律简略二字,却贯穿了我的前半生,我希望我能坚持做一件事,每天都能有所进步。