Javascript的迭代器和迭代接口详解

目录
  • 1,什么是迭代器
  • 2,自定义迭代接口
  • 3,原生语言的迭代
  • 总结

1,什么是迭代器

每一个可迭代对象都对应着一个可迭代接口[Symbol.iterator];

[Symbol.iterator]接口并不是迭代器,他是一个迭代器工厂函数,调用该迭代接口即可返回一个待执行状态的迭代器;

不同的原生全局对象都对应着不同的迭代器;

const arr = new Array()
const map = new Map()
const set = new Set()

console.log(arr[Symbol.iterator]()) //Array Iterator {}
console.log(map[Symbol.iterator]()) //MapIterator {}
console.log(set[Symbol.iterator]()) //SetIterator {}

将迭代器状态从待执行状态变为真正的执行:调用迭代器对象的 .next()方法;而其的返回值,就是next()方法的返回值对象:

const iterator = new Array(1, 2, 3, 4)[Symbol.iterator]()

console.log(iterator.next()) //{value: 1, done: false}
console.log(iterator.next()) //{value: 2, done: false}
console.log(iterator.next()) //{value: 3, done: false}
console.log(iterator.next()) //{value: 4, done: false}
console.log(iterator.next()) //{value: undefined, done: true}

可以看到,当我执行第四次的时候,也就是对应着arr[3],但此时返回的对象中,done属性依旧是false,而执行第五次时,value变成了undefined,done变成了true,为什么会出现这种情况呢

在解答这个问题之前,我们需要在重新认识一下迭代器:

本质上来说,迭代器对象就是实现了next()方法的对象

const myIterator = {
  next() {
    if (length) {
      return { value: 1, done: false }
    } else {
      return { value: undefined, done: true }
    }
  },
}

如上述,就是一个最简单的迭代器。调用next(),会执行迭代,返回done为false的迭代器生成对象,直到符合某种条件,返回done为true的迭代器生成对象。

你可以简单的把Array迭代器原理看作如下所示:

const myIterator = {
  next() {
    if (length) {
      return { value: 1, done: false }
    } else {
      return { value: undefined, done: true }
    }
  },
}

输出的结果也是一样的:

const iterator = new MyArray(1, 2, 3, 4)[Symbol.iterator]() 
console.log(iterator.next()) //{value: 1, done: false}
console.log(iterator.next()) //{value: 2, done: false}
console.log(iterator.next()) //{value: 3, done: false}
console.log(iterator.next()) //{value: 4, done: false}
console.log(iterator.next()) //{value: undefined, done: true}

那么,之前的问题就迎刃而解了。

或许你已经发现了,我自定义了一个类MyArray,并且我手动改写了他的迭代器接口。那么是否只要为某个不可迭代的对象,实现了[Symbol.iterator],就可以把它变成一个可迭代对象呢?

答案是肯定的。只要你想,你可以为任何对象加上可迭代协议,并把它变成可迭代对象。因为迭代对象的定义便是:实现迭代接口的对象。

2,自定义迭代接口

按照以上思路,我们就可以自己手动实现一个可迭代的Object对象了:

const prototype = {
  [Symbol.iterator]() {
    const entries = Object.entries(this)
    const { length } = entries
    let index = 0
    return {
      next() {
        return index < length
          ? {
              value: {
                key: entries[index][0],
                value: entries[index++][1],
              },
              done: false,
            }
          : { value: undefined, done: true }
      },
    }
  },
}

const obj = Object.create(prototype)
obj.name = 'zhang san'
obj.age = 28

const objIterator = obj[Symbol.iterator]()
console.log(objIterator.next()) //{value: {key:'name',value:'zhang san'}, done: false}
console.log(objIterator.next()) //{value: {key:'age',value:28}, done: false}
console.log(objIterator.next()) //{value: undefined, done: true}

首先,我们声明了一个改写了迭代接口的对象,接着用Obejct.create()创建了以此对象为原型的obj。

该对象实例本身是没有迭代接口的,但是会沿着原型链去寻找prototype对象是否存在迭代接口。只要能在其原型链上找到迭代接口,那么就代表其是一个可迭代对象。如:

const objSon = Object.create(obj, {
  name: {
    enumerable: true,
    writable: true,
    value: 'zhang xiao san',
  },
  age: {
    enumerable: true,
    writable: false,
    value: 2,
  },
  secret: {
    enumerable: false,
    writable: true,
    value: 'secret',
  },
})
const sonIterator = objSon[Symbol.iterator]()

console.log(sonIterator.next()) //{value: {key:'name',value:'zhang xiao san'}, done: false}
console.log(sonIterator.next()) //{value: {key:'age',value:2}, done: false}
console.log(sonIterator.next()) //{value: undefined, done: true}

objSon依旧是一个可迭代对象。

那么现在,我们就通过改造迭代接口[Symbol.iterator]的方式,把一个原本不是迭代类型的对象,变成了可迭代对象。

我们可以用该迭代接口来遍历任何enumerable的属性。但如果你想将enumrable为false的secret属性也遍历出来,那么只需要将迭代接口中的entries改造一下即可,一切皆由你想:

// const entries = Object.entries(this)

    const ownKeys = Object.getOwnPropertyNames(this)
    const entries = ownKeys.reduce(
      (result, key) => [...result, [key, this[key]]],
      []
    )

3,原生语言的迭代

以for - of 为例

for (const item of obj) {
  console.log(item)
}
//{key:'name',value:'zhang san'}
//{key:'age',value:28}

for (const item of objSon) {
  console.log(item)
}
//{key:'name',value:'zhang xiao san'}
//{key:'age',value:2}

可以看到,无论是obj,还是objSon,都可以正常用for of 循环,并且返回值为迭代器生成对象value属性的值。

你可以这么理解for - of 的机制:后台调用提供的可迭代对象的工厂函数[Symbol.iterator],从而创建一个迭代器,然后自动调用迭代器next执行。done为false,则将迭代器生成对象value赋值给item;done为true,则跳出循环:

const objIterator = obj[Symbol.iterator]()
{
  const { value: item,done } = objIterator.next()
  if(done) break
  console.log(item) //{key:'name',value:'zhang san'}
}
{
  const { value: item,done } = objIterator.next()
  if(done) break
  console.log(item) //{key:'age',value:28}
}
{
  const { value: item,done } = objIterator.next()
  if(done) break
  console.log(item)
}

不仅仅是for - of,原生语言的迭代机制,都与此类似。数组解构,拓展操作符,Array.from,new Set(),new Map(),Promise.all(),Promise.race(),yield * 操作符等,都属于原生迭代语言。

了解了for - of 循环的机制之后,大家可以观察一下下面的例子:

const arr = [1, 2, 3, 4, 5, 6, 7, 8]
for (const item of arr) {
  console.log(item)
  if (item > 3) break
}
for (const item of arr) {
  console.log(item)
}
//输出结果:1,2,3,4 | 1,2,3,4,5,6,7,8

const arrIterator = arr[Symbol.iterator]()
for (const item of arrIterator) {
  console.log(item)
  if (item > 3) break
}
for (const item of arrIterator) {
  console.log(item)
}
//输出结果:1,2,3,4 | 5,6,7,8

循环arr的输出结果与循环arr迭代器arrIterator的结果明显的不同。为什么会出现这种现象呢?

在迭代器对象中,还有一个很重要的知识点:迭代器对象是一个一次性的,不可逆的对象。

因此,在迭代中某个地方终止,那么只能接着上一次终止的位置继续执行,而不会从头开始。

那么为什么对于arr本身使用for - of,却没有接着执行而是从头开始呢?可以回到介绍for - of 循环机制的那部分,其中有一句话:后台调用提供的可迭代对象的工厂函数[Symbol.iterator],从而创建一个迭代器

也就是说,每调用一次for - of循环,都会创建一个新的迭代器对象,而该迭代器对象,在循环结束时就会被当作垃圾对象被回收。

虽然arr连续调用了两次for - of循环,但是在循环体的内部,并不是同一个迭代器对象。因此,即使上一个迭代器在item>3这个条件处中止了,但是下一次循环的迭代器对象,是一个全新的,还没有执行过的迭代器对象。

那么对于调用arr[Symbol.iterator]接口生成的的迭代器对象arrIterator,为什么会出现继续上次执行的情况呢,换句话说,为什么arrIterator的两次for循环,没有产生两次迭代器对象?

其实,迭代器对象本身,也实现了迭代器接口,也就是说。arr有一个迭代器接口[Symbol.iterator],而迭代器对象arrIterator也有一个迭代器接口[Symbol.iterator],并且,调用该迭代器对象,返回其本身:

console.log(arrIterator[Symbol.iterator]() === arrIterator) // true

所以虽然每次执行for-of循环都会同样的调用迭代器接口,但是该迭代器接口返回的对象就是arr的迭代器对象本身,而迭代器对象是一个一次性,不可逆的对象。因此,为什么会出现上述现象,也就显而易见了。

细心的朋友可能会想到一个问题:我们在之前手动实现的可迭代对象,其迭代器对象是否支持了迭代接口?是的,他不能

for (const item of objIterator) {
  console.log(item)
}
//Uncaught TypeError: objIterator is not iterable

但其实我们要优化也很简单,只要将该迭代器对象也实现一个迭代接口,并且该迭代接口工厂函数返回其本身,即可。

const prototype = {
  [Symbol.iterator]() {
    // const entries = Object.entries(this)

    const ownKeys = Object.getOwnPropertyNames(this)
    const entries = ownKeys.reduce(
      (result, key) => [...result, [key, this[key]]],
      []
    )
    const { length } = entries
    let index = 0
    return {
      next() {
        return index < length
          ? {
              value: {
                key: entries[index][0],
                value: entries[index++][1],
              },
              done: false,
            }
          : { value: undefined, done: true }
      },
      [Symbol.iterator]() {
        return this
      },
    }
  },
}

const objIterator = obj[Symbol.iterator]()

for (const item of objIterator) {
  console.log(item)
  if (item.key === 'name') break
}
for (const item of objIterator) {
  console.log(item)
}
// {key: 'name', value: 'zhang san'}
// {key: 'age', value: 28}

现在,你可以用文中所介绍的方法,将任何对象变成一个可迭代对象。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • JavaScript迭代器的含义及用法

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

  • javascript设计模式之迭代器模式

    迭代器模式分为内部迭代器和外部迭代器,内部迭代器就是在函数内部定义好迭代的规则,它完全接手整个迭代的过程,外部只需一次初始调用. 内部迭代器 以下自行实现的类似jquery中$.each()的each()函数就是内部迭代器 //实现一个jq的$.each()迭代器 var arr = [1, 2, 3, 4, 5, 6, 7, 8] var each = function(arr, callback){ for(var i=0; i<arr.length; i++){ callback.call

  • JavaScript数组迭代方法

    最近工作中经常涉及到数据的处理,数组尤其常见,经常需要对其进行遍历.转换操作,网上的文章零零散散,不得已自己又找出红宝书来翻出来看,顺便记一笔,便于以后查询. 数组常用的迭代方法 ECMAScript5为数组定义了5个迭代方法.每个方法都接受两个参数:要在每一项上运行的函数fn和(可选的)运行该函数的作用域对象--影响 `this` 的值. 传入这些方法中的函数(fn)会接收3个参数:item .index .array; 如: array.forEach(function(item,index

  • 学会javascript之迭代器

    目录 简介 js 中的迭代器是什么样子的 迭代协议 可迭代协议 迭代器协议 迭代过程 迭代总结 自定义迭代 传统写法 生成器函数写法 简介 迭代器是一种设计模式,可在容器对象 如 链表.数组上遍历,无需关心容器对象的内存分配的实现细节.简单的理解就是可以一个一个的依次拿到其中的数据,类似一个移动的指针,但是会告诉我们什么时候结束.这样我们可以拿到数据之后可以做一些我们需要做的事情. js 中的迭代器是什么样子的 在javascript 中迭代器是一个特殊对象,这个迭代器对象有一个next()方法

  • Javascript的迭代器和迭代接口详解

    目录 1,什么是迭代器 2,自定义迭代接口 3,原生语言的迭代 总结 1,什么是迭代器 每一个可迭代对象都对应着一个可迭代接口[Symbol.iterator]: [Symbol.iterator]接口并不是迭代器,他是一个迭代器工厂函数,调用该迭代接口即可返回一个待执行状态的迭代器: 不同的原生全局对象都对应着不同的迭代器: const arr = new Array() const map = new Map() const set = new Set() console.log(arr[S

  • javascript类型系统_正则表达式RegExp类型详解

    前面的话 前面已经介绍过javascript中正则表达式的基础语法.javascript的RegExp类表示正则表达式,String和RegExp都定义了方法,使用正则表达式可以进行强大的模式匹配和文本检索与替换.本文将介绍正则表达式的RegExp对象,以及正则表达式涉及 到的属性和方法 对象 javascript中的正则表达式用RegExp对象表示,有两种写法:一种是字面量写法:另一种是构造函数写法 Perl写法 正则表达式字面量写法,又叫Perl写法,因为javascript的正则表达式特性

  • Python 中迭代器与生成器实例详解

    Python 中迭代器与生成器实例详解 本文通过针对不同应用场景及其解决方案的方式,总结了Python中迭代器与生成器的一些相关知识,具体如下: 1.手动遍历迭代器 应用场景:想遍历一个可迭代对象中的所有元素,但是不想用for循环 解决方案:使用next()函数,并捕获StopIteration异常 def manual_iter(): with open('/etc/passwd') as f: try: while True: line=next(f) if line is None: br

  • Python 迭代器与生成器实例详解

    Python 迭代器与生成器实例详解 一.如何实现可迭代对象和迭代器对象 1.由可迭代对象得到迭代器对象 例如l就是可迭代对象,iter(l)是迭代器对象 In [1]: l = [1,2,3,4] In [2]: l.__iter__ Out[2]: <method-wrapper '__iter__' of list object at 0x000000000426C7C8> In [3]: t = iter(l) In [4]: t.next() Out[4]: 1 In [5]: t.

  • 实现JavaScript的组成----BOM和DOM详解

    我们知道,一个完整的JavaScript的实现,需要由三部分组成:ECMAScript(核心),BOM(浏览器对象模型),DOM(文档对象模型). 今天主要学习BOM和DOM. BOM: BOM提供了很多对象,用来访问浏览器的功能,这些功能于网页内容无关(这些是DOM的事),目前,BOM已经被W3C搬入了HTML5规范中. window对象: BOM的core,表示浏览器的一个实例,它既是通过javascript访问浏览器窗口的一个接口,又是ECMAScript规定的Global对象,这意味着在

  • java 中迭代器的使用方法详解

    java 中迭代器的使用方法详解 前言: 迭代器模式将一个集合给封装起来,主要是为用户提供了一种遍历其内部元素的方式.迭代器模式有两个优点:①提供给用户一个遍历的方式,而没有暴露其内部实现细节:②把元素之间游走的责任交给迭代器,而不是聚合对象,实现了用户与聚合对象之间的解耦. 迭代器模式主要是通过Iterator接口来管理一个聚合对象的,而用户使用的时候只需要拿到一个Iterator类型的对象即可完成对该聚合对象的遍历.这里的聚合对象一般是指ArrayList,LinkedList和底层实现为数

  • JavaScript代码异常监控实现过程详解

    这篇文章主要介绍了JavaScript代码异常监控实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 JavaScript异常一般有两方面:语法错误和运行时错误.两种错误的捕获和处理方式不同,从而影响具体的方案选型.通常来说,处理JS异常的方案有两种:try...catch捕获 和 window.onerror捕获.以下就两种方案分别分析各自的优劣. 虽然语法错误本应该在开发构建阶段使用测试工具避免,但难免会有马失前蹄部署到线上的时候.

  • JavaScript中window和document用法详解

    一.验证表单 封装一个函数用于验证手机号 /** * @param {String}eleId * @param {Object}reg */ function checkInput(eleId,reg) { var ele = document.getElementById(eleId); ele.onblur = function (ev) { if(!reg.test(this.value)){ //不匹配 this.style.borderColor = "#ff0000" /

  • JavaScript 拖拉时间之drag案例详解

    目录 DragEvent 接口 DataTransfer 接口概述 DataTransfer 的实例属性 DataTransfer.dropEffect DataTransfer.effectAllowed DataTransfer.files DataTransfer.types DataTransfer.items DataTransfer 的实例方法 DataTransfer.setData() DataTransfer.getData() DataTransfer.clearData()

  • 利用JavaScript获取用户IP属地方法详解

    目录 写在前面 尝试一:navigator.geolocation 尝试二:sohu 的接口 尝试三:百度地图的接口 写在后面 写在前面 想要像一些平台那样显示用户的位置信息,例如某省市那样.那么这是如何做到的, 据说这个位置信息的准确性在通信网络运营商那里?先不管,先实践尝试下能不能获取. 尝试一:navigator.geolocation 尝试了使用 navigator.geolocation,但未能成功拿到信息. getGeolocation(){ if ('geolocation' in

随机推荐