JavaScript中的迭代器和可迭代对象与生成器

目录
  • 1. 什么是迭代器?
    • 1.1 迭代器的基本实现
    • 1.2 迭代器的封装实现
  • 2. 什么是可迭代对象
    • 2.1 原生可迭代对象(JS内置)
      • 2.1.1 部分for of 演示
      • 2.1.2 查看内置的[Symbol.iterator]方法
    • 2.2 可迭代对象的实现
    • 2.3 可迭代对象的应用
    • 2.4 自定义类迭代实现
  • 3. 生成器函数
    • 3.1 生成器函数基本实现
    • 3.2 生成器函数单次执行
    • 3.3 生成器函数多次执行
    • 3.4 生成器函数的分段传参
    • 3.5 生成器代替迭代器
  • 4. 可迭代对象的终极封装
  • 5. 总结
    • 5.1 迭代器对象
    • 5.2 可迭代对象
    • 5.3 生成器函数

1. 什么是迭代器?

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

  • 其本质就是一个对象,符合迭代器协议(iterator protocol)
  • 迭代器协议

    其对象返回一个next函数

    调用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 }
  • 是一个对象,实现了next方法,next方法返回了一个对象,有done属性和value属性,且key的值类型也为booleanany,符合迭代器协议,是一个妥妥的迭代器没跑了。
  • 弊端
    • 违背了高内聚思想,明明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)
  • 可迭代对象协议

    实现了[Symbol.iterator]为key的方法,且这个方法返回了一个迭代器对象
  • 绕了一大圈终于把概念搞明白了,那可迭代对象有什么好处呢? 有什么应用场景呢?

    for of 的时候,其本质就是调用的这个函数,也就是[Symbol.iterator]为key的方法

2.1 原生可迭代对象(JS内置)

  • String
  • Array
  • Set
  • NodeList 类数组对象
  • Arguments 类数组对象
  • 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左侧接收的,是第二次调用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 迭代器对象

  • 本质就是一个对象,要符合迭代器协议
  • 有自己对应的next方法,next方法则返回一组数据{done:boolean, value:any}

5.2 可迭代对象

  • 本质就是对象,要符合可迭代对象协议
  • [Symbol.iterator]方法,并且调用这个方法返回一个迭代器

5.3 生成器函数

  • 可以控制函数的暂停执行和继续执行
  • 通过function* bar() {} 这种形式定义
  • 不会立马执行,而是返回一个生成器,生成器是一个特殊的迭代器对象
  • yield 关键字可以控制函数分段执行
  • 调用返回生成器的next方法进行执行

到此这篇关于JavaScript中的迭代器和可迭代对象与生成器的文章就介绍到这了,更多相关JavaScript迭代器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript前端迭代器Iterator与生成器Generator讲解

    目录 Iterator 概念 默认 Iterator 接口 Iterator 的 return() 原生具备 Iterator 接口的数据结构 调用 Iterator 接口的场合 模拟实现 for of Generator 认识 Generator next 方法的参数 yield 表达式 Generator 与 Iterator 之间的关系 Generator.prototype.return() yield* 表达式 Generator 函数的 this Generator 实现一个状态机

  • 总结javascript中的六种迭代器

    1.forEach迭代器 forEach方法接收一个函数作为参数,对数组中每个元素使用这个函数,只调用这个函数,数组本身没有任何变化 //forEach迭代器 function square(num){ document.write(num + ' ' + num*num + '<br>'); } var nums = [1,2,3,4,5,6,7,8]; nums.forEach(square); 在浏览器中输出的结果是: 2.every迭代器 every方法接受一个返回值为布尔类型的函数,

  • JavaScript详解类数组与可迭代对象的实现原理

    目录 可迭代对象(Iterable object) Symbol.iterator 把对象本身构造成迭代器 String也是可迭代的 String的迭代器 类数组对象和可迭代对象 Array.from 总结 可迭代对象(Iterable object) 数组是一个特殊的对象,它和普通对象的区别不仅仅在于元素的顺序访问.存储.另外一个重要的区别是:数组是可迭代的,也就是可以使用for ... of语句访问(迭代)所有的元素. 我们可以简单的做一个小实验: let arr = [1,2,3,4,5]

  • JavaScript可迭代对象详细介绍

    目录 1.迭代器 2.迭代器接口与可迭代对象 3.自定义可迭代对象 3.1.可迭代的Range对象 3.2.使用Generator函数作为迭代器接口 3.3.可迭代的List 3.3.可迭代的迭代器 4.可迭代对象的意义 5.使用可迭代对象 6.后记 1.迭代器 迭代器是借鉴C++等语言的概念,迭代器的原理就像指针一样,它指向数据集合中的某个元素,你可以获取它指向的元素,也可以移动它以获取其它元素.迭代器类似于数组中下标的拓展,各种数据结构,如链表(List).集合(Set).映射(Map)都有

  • JavaScript中的迭代器和生成器详解

    处理集合里的每一项是一个非常普通的操作,JavaScript提供了许多方法来迭代一个集合,从简单的for和for each循环到 map(),filter() 和 array comprehensions(数组推导式).在JavaScript 1.7中,迭代器和生成器在JavaScript核心语法中带来了新的迭代机制,而且还提供了定制 for-in 和 for each 循环行为的机制. 迭代器 迭代器是一个每次访问集合序列中一个元素的对象,并跟踪该序列中迭代的当前位置.在JavaScript中

  • JavaScript迭代器的含义及用法

    什么是迭代器 迭代器就是为实现对不同集合进行统一遍历操作的一种机制,只要给需要遍历的数据结构部署Iterator接口,通过调用该接口,或者使用消耗该接口的API实现遍历操作. 迭代器模式 在接触迭代器之前,一起先了解什么是迭代器模式,回想一下我们生活中的事例.我们在参观景区需要买门票的时候,售票员需要做的事情,他会对排队购票的每一个人依次进行售票,对普通成人,对学生,对儿童都依次售票.售票员需要按照一定的规则,一定顺序把参观人员一个不落的售完票,其实这个过程就是遍历,对应的就是计算机设计模式中的

  • JavaScript中的迭代器和可迭代对象与生成器

    目录 1. 什么是迭代器? 1.1 迭代器的基本实现 1.2 迭代器的封装实现 2. 什么是可迭代对象 2.1 原生可迭代对象(JS内置) 2.1.1 部分for of 演示 2.1.2 查看内置的[Symbol.iterator]方法 2.2 可迭代对象的实现 2.3 可迭代对象的应用 2.4 自定义类迭代实现 3. 生成器函数 3.1 生成器函数基本实现 3.2 生成器函数单次执行 3.3 生成器函数多次执行 3.4 生成器函数的分段传参 3.5 生成器代替迭代器 4. 可迭代对象的终极封装

  • ES6 迭代器与可迭代对象的实现

    ES6 新的数组方法.集合.for-of 循环.展开运算符(...)甚至异步编程都依赖于迭代器(Iterator )实现.本文会详解 ES6 的迭代器与生成器,并进一步挖掘可迭代对象的内部原理与使用方法 一.迭代器的原理 在编程语言中处理数组或集合时,使用循环语句必须要初始化一个变量记录迭代位置,而程序化地使用迭代器可以简化这种数据操作 如何设计一个迭代器呢? 迭代器的本身是一个对象,这个对象有 next( ) 方法返回结果对象,这个结果对象有下一个返回值 value.迭代完成布尔值 done,

  • JavaScript中Iterator迭代器接口和循环

    目录 JavaScript的迭代器(Iterator)介绍 for...of循环与for...in循环 JavaScript的迭代器(Iterator)介绍 迭代器是数据结构遍历的一种机制(或者是什么我也不太懂的行业术语),为数据结构定义了统一的遍历规则. 迭代器(Iterator)主要是提供for...of使用,或者说,数据结构只有实现了迭代器(Iterator)接口才能使用for...of进行遍历数据,当一种数据结构没有实现迭代器(Iterator)接口时,去使用for...of操作,就会被

  • JavaScript中重名的函数与对象示例详析

    前言 本文主要给大家介绍了关于JavaScript中重名的函数与对象的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. JavaScript 允许重复声明变量,后声明的覆盖之前的. var a = 1; var a = 'x'; console.log(a); //输出'x' JavaScript允许重复定义函数. JavaScript没有重载这个概念,它仅依据函数名来区分函数. 后定义的同名函数覆盖之前的,与参数无关. function test() { consol

  • 详解JavaScript中的数组合并方法和对象合并方法

    1 数组合并 1.1 concat 方法 var a=[1,2,3],b=[4,5,6]; var c=a.concat(b); console.log(c);// 1,2,3,4,5,6 console.log(a);// 1,2,3 不改变本身 1.2 循环遍历 var arr1=['a','b']; var arr2=['c','d','e']; for(var i=0;i<arr2.length;i++){ arr1.push(arr2[i]) } console.log(arr1);/

  • JavaScript 中的单例内置对象Global 与 Math

    目录 前言 Global 1.URL编码方法 2.eval()方法 3.Gobal对象属性 4.window 对象 Math 1.Math 对象属性 2.min()和max()方法 3.舍入方法 Math.ceil(),Math.floor(),Math.round(),Math.fround() random()方法 前言 ECMA-262 对内置对象的定义是“任何由ECMAScript实现提供,与宿主环境无关,并在ECMAScript 程序开始执行时就存在的对象”.这意味着,开发者不用显示的

  • 解析JavaScript中delete操作符不能删除的对象

    ES3 中,delete在8.6.2.5及11.4.1有介绍,如下 有一些信息, 1.实现上delete操作符会调用引擎内部的[[Delete]]方法 2.[[Delete]]在8.6.2里定义 3.删除的属性有个DontDelete的特性,如果有,delete时直接返回false 搜索"DontDelete",会发现有很多,如下都不能delete 1, 激活对象的arguments对象 (10.1.6) 复制代码 代码如下: function func() {     delete

  • 浅析JavaScript中两种类型的全局对象/函数

    这里所说的JavaScript指浏览器环境中的包括宿主环境在内的.第一种是ECMAScript Global Object,第二种是宿主环境(Host)下的全局对象/函数. 一.核心JavaScript内置对象,即ECMAScript实现提供的不依赖于宿主环境的对象 这些对象在程序执行之前就已经(实例化)存在了.ECMAScript称为The Global Object,分为以下几种 1, 值属性的全局对象(Value Properties of the Global Object).有NaN,

  • Javascript中判断变量是数组还是对象(array还是object)

    怎样判断一个JavaScript变量是array还是obiect? 答案: 1.如果你只是用typeof来检查该变量,不论是array还是object,都将返回'objec'. 此问题的一个可行的答案是是检查该变量是不是object,并且检查该变量是否有数字长度(当为空array时长度也可能为0). 然而,参数对象[arguments object](传给制定函数的所有参数),也可能会适用于上述方法,技术上来说,参数对象并不是一个array. 此外,当一个对象有a.length属性的时候,这个方

随机推荐