JavaScript基础:Iterator概念及用法

2023-10-27

在这里插入图片描述

一、由来及意义

Javascript中表示“集合”的数据结构,主要是 ArrayObjectMapSet 这四种数据集合,除此之外,它们相互之间还可以组合使用,例如Array的成员是MapMap的成员是Object等。因此Javascript的需要一种统一的接口机制,来处理所有不同的数据结构。

遍历器(Iterator)就是这样一种机制。它是一种接口,可以为各种不同的数据结构提供一种访问机制(访问接口),任何数据结构部署Iterator接口,就可以完成该数据解构成员的遍历操作(Iterator 接口主要供for...of使用)。

二、具体实现流程

Iterator的遍历过程:

  1. 创建一个指针对象,指向数据解构的起始位置。
  2. 第一次调用指针对象的next()方法,指针指向数据结构的第一个成员。
  3. 第二次调用指针对象的next()方法,指针指向数据结构的第二个成员。
  4. 不停调用指针对象的next()方法,直到它指向数据结构结束的位置。(类似于C语言中的链表)

每一次调用next方法,都会返回数据结构中被指针指向的成员的信息。该信息为一个对象,其中包含valuedone两个属性的对象{ value: something , done: false }value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束(done:false:表示循环还没有结束,done:true :表示循环结束了)。

模拟next方法返回值例子:

下面代码定义了一个makeIterator函数,它是一个遍历器生成函数,作用就是返回一个遍历器对象。对数组['前端','收割','机']执行这个函数,就会返回该数组的遍历器对象(即指针对象)goodjob

var goodjob = makeIterator(['前端','收割','机'])

function makeIterator(array){
    
    var index = 0;//形成闭包保存i指针指向位置index,通过三元表达式返回当前信息对象。
    
    return {
        next: function(){
            return Index < array.length ? {value: array[index], done: false} : 
            {value: undefined, done: true};
        }
    }
}

goodjob.next()//  {value: '前端', done: false}
goodjob.next()//  {value: '收割', done: false}
goodjob.next()//  {value: '机',  done: false}
goodjob.next()//  {value: undefined, done: true}

由于 Iterator 只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构。

三、具有默认 Iterator 接口的数据结构

Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。

因此,当某种数据结构具有Iterator 接口,即表示该数据结构是可遍历的(iterable)。

默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内。

const object1 = {
  [Symbol.iterator] : function () {
    return {
      next: function () {
        return {
          value: 1,
          done: true
        };
      }
    };
  }
};

对象object1是可遍历的(iterable),因为具有Symbol.iterator属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有next方法。每次调用next方法,都会返回一个代表当前成员的信息对象,具有valuedone两个属性。

凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。即不用任何处理,就可以被for...of循环遍历。

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

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • NodeList 对象
  • 函数的 arguments 对象

数组的Symbol.iterator属性:

let arr = ['前端', '收割', '机'];
let iterator = arr[Symbol.iterator]();  //因为arr中属性Symbol.iterator返回的是一个函数,
                                        //所以在[Symbol.iterator]后面添加(),使函数执行,返回一个遍历器对象

iterator.next() // { value: '前端', done: false }
iterator.next() // { value: '收割', done: false }
iterator.next() // { value: '机', done: false }
iterator.next() // { value: undefined, done: true }

数组通过for of调用iterator接口生成的遍历器

const arr = ['前端', '收割', '机'];

for(let v of arr) {
  console.log(v); // 前端 收割 机
}

Map通过for of调用iterator接口生成的遍历器

var handsome = new Map();
es6.set("GuangHui", "1handsome");
es6.set("JiaHao", "2handsome");
es6.set("NingDong", 666);
for (var [name, value] of es6) {
  console.log(name + ": " + value);
}
// GuangHui: 1handsome
// JiaHao: 2handsome
// NingDong: 666

Set 通过for of调用iterator接口生成的遍历器

var handsome = new Set(["GuangHui", "JiaHao", "NingDong"]);
for (var boy of handsome) {
  console.log(boy);
}
// GuangHui
// JiaHao
// NingDong

类数组对象通过for of调用iterator接口生成的遍历器

// 字符串
let str = "前端收割机";

for (let s of str) {
  console.log(s); // 前 端 收 割 机
}

// DOM NodeList对象
let paras = document.querySelectorAll("p");

for (let p of paras) {
  p.classList.add("前端收割机");
}

// arguments对象
function printArgs() {
  for (let x of arguments) {
    console.log(x);
  }
}
printArgs('前端', '收割机');
// '前端'
// '收割机'

对于不具备Iterator接口的数据结构(主要是对象)都需要自己在Symbol.iterator属性上面部署,这样才会被for...of循环遍历。

原因:对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。

下面是为对象添加 Iterator 接口的例子:

let object2 = {
  data: [ '前端', '收割','机'],
  [Symbol.iterator]() {
    const self = this; //将this指向赋予self
    let index = 0;     //初始遍历下标
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++], //每次调用,遍历下标自增1
            done: false
          };
        } else {
          return { value: undefined, done: true }; //遍历结束返回该对象
        }
      }
    };
  }
};

对于类似数组的对象(存在数值键名和length属性),部署 Iterator 接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的 Iterator 接口。

let object3 = {
  0: '前端',
  1: '收割',
  2: '机',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator] //直接引用数组构造函数prototype中的 Symbol.iterator属性
};
for (let item of iterable) {
  console.log(item); // '前端', '收割', '机'
}

注意,普通对象部署特定数据结构的Symbol.iterator方法,并无效果。例如普通对象部署数组的Symbol.iterator方法。

let object4 = {  //该对象不存在数值键名
  a: '前端',
  b: '收割',
  c: '机',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
  console.log(item); // undefined, undefined, undefined
}

如果Symbol.iterator方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。

var object5 = {};

obj[Symbol.iterator] = () => '前端收割机';  //返回的是一个字符串

[...object5]  // TypeError: [] is not a function

四、调用 Iterator 接口的场合

除了for...of循环,某些场景会默认调用 Iterator 接口(即Symbol.iterator方法)

解构赋值

let set = new Set().add('前端').add('收割').add('机'); //Set通过add方法进行链式添加值

let [one,two] = set;
// x='前端'; y='收割'

let [one, ...two] = set;
// one='前端'; two=['收割','机'];

扩展运算符

// 例一
var str = '前端收割机';
[...str] //  ['前','端','收','割','机']

// 例二
let arr = ['是', '靓'];
['我', ...arr, '仔']
// ['我', '是', '靓', '仔']

扩展运算符内部就调用 Iterator 接口,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,通过扩展运算符,转为数组。

yield*

yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

let generator = function* () {
  yield "我";
  yield* ["是","靓","仔"];
  yield "啊";
};

var iterator = generator();

iterator.next() // { value: "我", done: false }
iterator.next() // { value: "是", done: false }
iterator.next() // { value: "靓", done: false }
iterator.next() // { value: "仔", done: false }
iterator.next() // { value: "啊", done: false }
iterator.next() // { value: undefined, done: true }

Array.from()

Array.from()函数以类数组形式输入值,Array.from()函数调用Iterator接口,将输入的类数组转化成数组

let arrayLike = {
    0: '前端', 
    1: '收割',
    2: '机',
    3: ['GuangHui','JiaHao','NingDong'],
    'length': 4
}
let arr = Array.from(arrayLike)
console.log(arr) // ['前端','收割','机',['GuangHui','JiaHao','NingDong']]

Map(), Set(), WeakMap(), WeakSet()

Map构造函数以数组形式输入键值对,新建Map对象时,Map构造函数调用Iterator接口,遍历存入键值对

var goodJob  = new Map([['前端',1],['收割机',2]]) 

Promise.all()

Promise.all()函数以Promise数组形式输入值,Promise.all()函数调用Iterator接口,将promise请求遍历执行。

const p = Promise.all([p1, p2, p3]);

Promise.all()方法接受一个数组作为参数,p1p2p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

Promise.race()

Promise.race()函数以Promise数组形式输入值,Promise.race()函数调用Iterator接口,将promise请求遍历执行。

const p = Promise.race([p1, p2, p3]);

Promise.race()方法接受一个数组作为参数,p1p2p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.race()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

补充:Generator 函数与 Iterator 接口的关系:

对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。

五、总结

  1. 遍历器(Iterator)可以为各种不同的数据结构提供一种访问机制(访问接口),任何数据结构部署Iterator接口,就可以完成该数据解构成员的遍历操作(Iterator 接口主要供for...of使用)。
  2. Iterator的遍历过程:创建一个指针对象,指向数据解构的起始位置。不停调用指针对象的next()方法,指针往后移动,直到它指向数据结构结束的位置。每一次调用next方法,都会返回数据结构中被指针指向的成员的信息{ value: something , done: false }
  3. ArrayMapSetStringTypedArrayNodeList 对象,函数的 arguments 对象为原生具备 Iterator 接口的数据结构。
  4. 调用 Iterator 接口的场合:for...of循环、解构赋值、扩展运算符、yield*Array.from()Map()Set()WeakMap()WeakSet()Promise.all()Promise.race()
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaScript基础:Iterator概念及用法 的相关文章

  • 跨域iframe自动调整大小

    我正在使用 iframe 和 javascript 制作一个 Web 小部件 我想让我的 iframe 能够根据其内容 从其他域加载 调整大小 我做了一些搜索 包括在 Stack Overflow 上 我找到了几个主题 但我找不到关于这个问
  • Javascript 函数与 php 一样吗?

    我在网站上使用 WebIM 提供聊天支持 我希望能够在客户端启动聊天会话时设置一个计时器 如果操作员 技术人员在 x 秒内没有响应 我希望页面重定向到客户端可以留言的另一个页面 有点像 请稍等 我们尝试联系您 这样 如果所有技术人员都太忙或
  • 当名称是数组时如何使用 Javascript 修改 HTML Select

    我有两个同名的 html select 对象 它们是具有不同索引的数组 我想做的是 如果从类别 0 选择元素中选择 关闭 我想禁用类别 1 元素 我一直在尝试使用 document getElementsByName 但无法弄清楚如何专门针
  • jQuery - 将所有展开的文本包装在 p 标签中

    我遇到以下情况 以下代码被写入我的页面 div Some text here which is not wrapped in tags p Some more text which is fine p p Blah blah another
  • 使用javascript在谷歌地图中绘制多边形

    我想在谷歌地图中绘制多边形 我正在使用示例代码http jsfiddle net rvsMH 1 http jsfiddle net rvsMH 1 但它不起作用并且没有获取数组或纬度 经度值 我不知道出了什么问题
  • 如何使用 AngularJS、Devise 和 UI Router 全局实现身份验证?

    我对 Angular 很陌生 所以这可能是一个新手问题 我正在尝试实现一个简单的任务管理器 只是一个练习 以 Rails 作为后端 以 Angular 作为前端 到目前为止 我遵循了教程 一切正常 现在我想在全球范围内实施身份验证 这意味着
  • javascript中文本区域限制每行的字符数

    我试图用 javascript 限制文本区域中每行的字符数 我在这里看到了一些例子 但并不完全符合我的要求 我写了一些东西 只有当你每次添加超过限制时才可以 换句话说 我每行有 10 个字符的限制 如果你总是输入至少 10 个字符就可以正常
  • 如何将元素的每个单词包装在span标签中?

    div date contents filter function return this nodeType 1 wrap span span 我是新手 认为代码可以解决问题 但它将所有内容都包含在 span 像这样 div class d
  • 使用什么事件来在选择文本框中的值时显示警报消息

    我正在使用 jquery 的自动完成 api 来从数据库中获取名称 但是我想在从显示的文本框中选择名称时显示一条警报消息 我将显示一个图像以便更好地理解 当我输入 S 时 它将显示所有包含 S 的记录 所以问题是 如果我选择例如 Spars
  • JavaScript 逻辑赋值是如何工作的?

    在 javascript 中 如果我们有一些代码 例如 var a one var b q a alert b 逻辑 OR 运算符会将 a 的值分配给 b 并且警报将为 一 这仅限于作业还是我们可以在任何地方使用它 似乎空字符串被视为与未定
  • JQuery 可排序嵌套可排序 div

    这个问题与这个有关Nest jQuery UI 可排序 https stackoverflow com questions 19129476 nest jquery ui sortables 但我无法解决我的问题 问题是 我有一个包含项目的
  • 无法读取setInterval(Hooks)中的最新状态变量[重复]

    这个问题在这里已经有答案了 我想这是因为 JS 的工作原理 但我想你不会在类中遇到这个问题 在此代码中 let open setOpen React useState false let counter setCounter React u
  • 设置股票数据 Highcharts xAxis 的格式

    我已经浏览了需要为 xAxis 属性设置的 Highcharts 选项来格式化时间标签 但没有运气了解这对于这种情况到底是如何工作的 我在白天 盘中 检索了股票的动态数据 我需要显示这些数据 因为检索的数据每天从 9 30 开始到 17 0
  • json、rails、javascript 中的解析错误

    我需要将 ruby 数组放入 javascript 数组中 但出现解析错误 var characters 这就是我将 ruby 嵌入到内联 javascript 中的方式 但它出现了解析错误 我应该如何将此 ruby 数组放入 javasc
  • Material.Angular.io mat-autocomplete [displayWith] 函数更新范围变量

    我遇到了一个问题 我可以在实例化 mat autocomplete 的组件控制器中访问本地声明的变量 我面临的问题是局部变量被困在这个范围内 我无法更新它们 有关更新 mat autocomplete 范围变量的任何想法或想法 最终我要做的
  • 我将 X Y Z 坐标转换为屏幕 X Y 坐标有什么问题吗?

    我正在制作 3D 空间中弹跳的球体的 HTML5 Canvas 演示 这非常简单 每个球都有 X Y 和 Z 坐标 然后 这些坐标将转换为我在此处阅读的屏幕 X 和 Y 坐标 http answers google com answers
  • Jquery获取每个div的子子div并将信息抓取到数组中

    我有一些看起来像这样的 html div div class sub main div div
  • backbone.js:视图中影响集合中不同模型的按钮

    我刚刚开始使用backbone js 到目前为止 我真的很喜欢它 我有这样的事情 ModelA ModelB ViewA ViewB ModelA 持有 ModelB 的集合 如何使用按钮构建模型 B 的视图 单击该按钮会更改集合中下一个
  • 通过ajax执行后期操作时如何克服CORS重定向问题?

    我可以通过外部登录表单中的 post 方法类型提交表单来登录 roundcube 实例 托管在另一台服务器上 我收到此错误 通过 ajax 签名时 XMLHttpRequest 无法加载https 192 168 0 7 mail http
  • 将日期参数传递给对 MVC 操作的 ajax 调用的安全方法

    我有一个 MVC 操作 它的参数之一是DateTime如果我通过 17 07 2012 它会抛出一个异常 指出参数为空但不能有空值 但如果我通过01 07 2012它被解析为Jan 07 2012 我将日期传递给 ajax 调用DD MM

随机推荐

  • vulnhub-SKYTOWER: 1靶场夺旗之战

    以下演示均在测试环境进行 遵纪守法 共建网络安全 靶场下载地址 SkyTower 1 VulnHub 是一个VBOX的虚拟机文件 用vbox打开或者用如下命令转换为vm文件 VBoxManage exe clonehd 绝对路径 SkyTo
  • 怎么解决Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection

    在python中安装包时发生了如图所示bug 原因是连接超时 所以需要自己设定安装源 解决方法 在 pip命令后自己设定收集源 i url eg pip install requests i http pypi douban com sim
  • LeetCode146. LRU缓存机制

    题目来源 https leetcode cn com problems lru cache submissions 题目描述 代码如下 方式一 LRU实现手段就是哈希表 链表 而LinkedHashMap正好存在该特性 因此利用Linked
  • 符号表

    符号表 每一个符号表对象都提供getAt函数来获取指定名称记录 Acad ErrorStatus getAt const char pEntryName 需要获取对象的名称 AcDb OpenMode mode 打开方式 XXXTableR
  • linux MongoDB 安装与配置

    目录 MongoDB概述 MongoDb安装部署 MongoDB设置密码 MongoDB操作命令与说明 配置文件说明 备份操作 还原操作 MongoDB实际场景应用 MongoDB概述 MongoDB是一个非关系型数据库管理系统 它使用文档
  • python注意事项(一)

    1 Python中input接收到的是字符串类型 2 i j k 的表达形式 print s s s i j k ctrl s 是保存的功能 4 在print中加上end 结束换行 例如print i end print 可以实现换行效果
  • 求1-1/2+1/3-1/4+....+1/100的和 (C语言)

    代码 include
  • linux的汇编指令大全,Linux 汇编指令集

    注意 汇编语言下操作所有的数据必须取出数据放到通用寄存器中处理 完毕之后从通用寄存器中读取出放到指定的寄存器中 1 汇编程序书写格式 声明程序段时不要顶格写 如下 AREA test CODE READONLY 声明代码段 段名 类型 属性
  • C#之static的用法详解

    有的东西你天天在用 但未必就代表你真正了解它 正如我之前所了解的static 一 静态类 静态类与非静态类的重要区别在于静态类不能实例化 也就是说 不能使用 new 关键字创建静态类类型的变量 在声明一个类时使用static关键字 具有两个
  • linux——递归遍历目录的实现

    一 递归目录分析 2 实现 include
  • ehcache springboot_SpringBoot入门建站全系列(十三)本地缓存的使用(Ehcache和caffeine的使用)

    SpringBoot入门建站全系列 十三 本地缓存的使用 Ehcache和caffeine的使用 一 概述 本地缓存 就是使用应用内使用本地内存将数据暂缓存储 一般数据库的查询如果不怎么改动 可以用本地缓存暂存 远程缓存 比如redis 就
  • (支付接口)签名验签和请求参数加密方法

    当我们在测试支付接口的时候 发现每个接口需要将请求的敏感数据加密后才能请求接口 遇到这种问题怎么解决呢 特别是当我们在写python接口自动化测试脚本的时候 这是必须要解决的问题 下面我来详细的写下这个过程 首先 我们需要拿到商户的公钥和公
  • Flex学习笔记2——第一个Flex程序

    Flex的调试 1 使用trace 相当于alert 2 使用firefox调试 下载debug版本的firefox flash player插件 下载插件HttpFox FlashTracer Cache Status 三个插件 Flas
  • 使用Frida对Windows平台的程序进行逆向分析(转)

    https developer 51cto com art 202009 626689 htm
  • 【深度学习基本概念】上采样、下采样、卷积、池化

    上采样 概念 上采样 upsampling 又名放大图像 图像插值 主要目的是放大原图像 从而可以显示在更高分辨率的显示设备上 上采样有3种常见的方法 双线性插值 bilinear 反卷积 Transposed Convolution 反池
  • 海森矩阵及其应用

    海森矩阵及其应用 转载 2017年04月20日 09 59 48 标签 梯度下降算法 微积分 牛顿迭代法 原文参考链接 here 原文讲得到很详细 海森矩阵 在数学中 海森矩阵 Hessian matrix或Hessian 是一个自变量为向
  • 【无人机 学习笔记 2】无人机导航制导与控制

    梳理完控制以及飞行力学的一些基本原理之后 就该正式进入无人机导航 制导与控制的讨论了 导航制导与控制是无人机系统中最复杂的分系统 其功能可以有多种划分方法 本文中 我们就以下面框图所示的划分方法为例 对无人机导航制导与控制系统的基本原理和常
  • numpy 查找元素位置 numpy.where

    numpy where condition x y 详细用法请大家详见官方文档 这里举几个例子 import numpy as np a np array 1 2 3 4 5 print np where a lt 3 查找小于3的元素的位
  • Python学习笔记(CSDN)- 1 - Python简介

    按照CSDN上的课程进行学习 Python简介 预备知识 CSDNPython入门技能树 先是附上这门课的思维导图 目录 1 Python定义 2 Python发展史 2 1 Python的发展 2 2 Python的版本更新 2 3 Py
  • JavaScript基础:Iterator概念及用法

    一 由来及意义 Javascript中表示 集合 的数据结构 主要是 Array Object Map Set 这四种数据集合 除此之外 它们相互之间还可以组合使用 例如Array的成员是Map Map的成员是Object等 因此Javas