前端开发系列125-进阶篇之Iterator
Iterator是一种接口,为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署实现了 Iterator 接口,就可以完成遍历操作。
Iterator 的优点在于能够为不同的数据结构提供了统一的接口;能够以特定的排序来遍历数据结构;提供创造了for...of循环。JavaScript中默认实现迭代器接口(Iterator)的数据结构有类数组结构(NodeList、arguments、String等) 和 Set 、Map、Array等 ,实现 Iterator 接口的数据结构均支持使用 for...of 循环来执行遍历操作。

下面通过代码简单展示Set 、Map、Array三种数据结构中实现的原生迭代器接口(Iterator)和for...of遍历。
/* 1.数组 Array */
/* 2.集合 Set */
/* 3.映射 Map */
/* 4.其它结构 */
let arr = [100, 200, 300];
console.log("arr", arr);
let set = new Set([10, 20, 30, 20, "测试"]);
console.log("set", set);
let map = new Map();
map.set("a", "A");
map.set("b", "B");
map.set("c", "C");
console.log("map", map);
/* 测试数据 */
for (let ele of arr) {
console.log(ele);
}
console.log('______________');
for (let ele of set) {
console.log(ele);
}
console.log('______________');
for (let [key,val] of map) {
console.log(key,val);
}
console.log('______________');
/* 打印输出 */
/*
arr [ 100, 200, 300 ]
set Set { 10, 20, 30, '测试' }
map Map { 'a' => 'A', 'b' => 'B', 'c' => 'C' }
100
200
300
______________
10
20
30
测试
______________
a A
b B
c C
______________ */
通过查看console.log(Array.property,Set.property,Map.property);打印结果,你会发现在数组、集合和映射它们的内部,都在其构造函数的原型对象上无一例外都实现了Symbol(Symbol.iterator): ƒ entries()函数,调用该函数我们能够得到一个iterator 型对象,当我们使用for...of循环结构来遍历它们的时候,在内部会利用该对象来完成遍历操作。
let arr = [100, 200, 300];
/* arr.__proto__ === Array.prototype[Symbol.iterator] */
let iterator = arr[Symbol.iterator]();
console.log(iterator); /* Object [Array Iterator] {} */
console.log("_________bgn_________")
let o = iterator.next();
while (!o.done) {
o = iterator.next()
console.log(o);
}
console.log("_________end_________")
/* 打印输出 */
/*
Object [Array Iterator] {}
_________bgn_________
{ value: 200, done: false }
{ value: 300, done: false }
{ value: undefined, done: true }
_________end_________
*/
通过Array.prototype[Symbol.iterator]()可以得到一个iterator 型对象,调用该对象的next方法后能得到个拥有两个键值对的对象,其中value表示的是当前的值,而 done 可以理解为是循环是否结束。在上面的代码中,我通过一个 while 循环来模拟了for..of循环过程。此外,也可以简单对比下这些结构中的entries()、keys() 和 values()等函数的用法。
let arr = [100, 200, 300];
let iterator = arr.entries();
console.log("_________bgn_________")
let o = iterator.next();
while (!o.done) {
o = iterator.next()
console.log(o);
}
console.log("_________end_________")
console.log(arr.keys())
console.log(arr.values())
/* 测试输出 */
/*
_________bgn_________
{ value: [ 1, 200 ], done: false }
{ value: [ 2, 300 ], done: false }
{ value: undefined, done: true }
_________end_________
Object [Array Iterator] {}
Object [Array Iterator] {} */
在 ES6中的数组、Set和 Map 中都部署了entries() 、keys()、values() 三个方法,它们调用后都返回 iterator 迭代器对象,其中entries()返回的迭代器对象用于遍历[key,value]组成的数组,而keys()返回的迭代器对象用于遍历所有的键名,values()返回的迭代器对象用于遍历所有的键值。除了上面列出的Array、Set和 Map结构支持for..of外,下面在给出类数组结构(伪数组)的几种情况。
/* 1. arguments */
function test() {
console.log('arguments');
for (const iterator of arguments) {
console.log('iterator = ', iterator);
}
}
test("a", "b", "c", 10, 203);
/* 打印输出: */
/*
arguments
iterator = a
iterator = b
iterator = c
iterator = 10
iterator = 203 */
/* 2.NodeList */
let oDiv = document.createElement("div");
oDiv.innerHTML = "<span>A</span><span>B</span><span>c</span><span>D</span>";
console.log(oDiv.children);
for (const iterator of oDiv.children) {
console.log('element = ', iterator);
}
/* 打印输出 */
/*
HTMLCollection(4) [span, span, span, span]
VM76:5 element = <span>A</span>
VM76:5 element = <span>B</span>
VM76:5 element = <span>c</span>
VM76:5 element = <span>D</span> */
/* 3.字符串(String) */
let str = "Hello";
for (const iterator of str) {
console.log("s = ", iterator);
}
for (const iterator of str[Symbol.iterator]()) {
console.log("s = ", iterator);
}
/* 打印输出 */
/*
s = H
s = e
s = l
s = l
s = o */
在上面列出的几种伪数组结构中,他们内部都实现了iterator接口,自己写的伪数组或者是对象实现了iterator接口支持for...of循环吗? 答案是否定的。
/* 1、自己写的伪数组结构 */
let likeArray = { 0: "a", 1: "b", 2: 'c', length: 3 };
for (const iterator of likeArray) {
console.log('iterator = ', iterator);
}
/* 报错:TypeError: likeArray is not iterable */
/* 2.对象结构 */
let o = { name: "Yong", age: 18 };
for (const iterator of o) {
console.log('iterator = ', iterator);
}
/* 报错:TypeError: o is not iterable */
如果自己写的伪数组也要能够支持for...of 循环,那么可以有下面几种尝试的办法。
let likeArray = { 0: "a", 1: "b", 2: 'c', length: 3 };
/* 第一种方式:通过对象解构方式来先转换为数组 */
/* 结果:失败 (分析原因:扩展运算符[...]内部默认会自动调用 iterator 接口) */
for (const iterator of[...likeArray]) {
console.log('iterator = ', likeArray);
}
/* 第二种方式:利用 Array.from尝试转换为数组 */
for (const iterator of Array.from(likeArray)) {
console.log('iterator = ', iterator);
}
/* 结果输出:
iterator = a
iterator = b
iterator = c */
/* 第三种方式:在当前伪数组的原型上面部署"原生"的 iterator 迭代器接口 */
/* ① */
// likeArray.__proto__[Symbol.iterator] = Array.prototype[Symbol.iterator];
/* ② */
// Object.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
/* ③ */
// likeArray[Symbol.iterator] = Array.prototype[Symbol.iterator];
/* ④ */
likeArray[Symbol.iterator] = [][Symbol.iterator].bind(likeArray);
for (const iterator of likeArray) {
console.log('iterator = ', iterator);
}
/* 结果输出:
iterator = a
iterator = b
iterator = c */
/* 2.对象结构 */
let o = { name: "Yong", age: 18 };
/* 2.1 对象无法直接通过 for...of进行遍历 */
for (const iterator of o) {
console.log('iterator = ', likeArray);
}
/* 报错:TypeError: o is not iterable */
/* 2.2 尝试利用数组的Symbol.iterator接口部署 */
o.__proto__[Symbol.iterator] = [][Symbol.iterator];
for (const iterator of o) {
console.log('iterator = ', likeArray);
}
/* 结果:不会进入循环,没有任何输出 */
/* 2.3 尝试遍历对象的 keys 间接遍历对象 */
for (const key of Object.keys(o)) {
console.log(`key:${key} value:${o[key]}`);
}
/* 结果 */
/*
key:name value:Yong
key:age value:18 */
这里简单思考和总结下,对象中没有实现
Iterator迭代器的原因
○ 对象已经拥有了 for...in循环 (该循环专为对象迭代设计)。
○ 对象在遍历的时候,属性( 键值对 )遍历的先后顺序是不确定的,而Iterator迭代器是线性的。
○ ES6提供了 Map ,可以在某种程度上实现替代操作。
在数组等数据结构中,当我们调用 entries() 或者是Symbol.iterator()的时候将得到一个iterator迭代器对象,在该对象中next方法每调用一次就会返回一个包含本次迭代 value 值以及标记是否完成迭代的 done 属性。
let arr = ["a", "b"];
let iterator = arr[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
/* 打印输出 */
/*
{ value: 'a', done: false }
{ value: 'b', done: false }
{ value: undefined, done: true } */
这里我们可以尝试来封装一个函数makeIterator,模拟 next函数的工作过程。
let makeIterator = (arr) => {
let idx = 0;
return {
next: () => idx < arr.length ?
{ value: arr[idx], done: false } : { value: undefined, done: true }
}
}
let iterator = makeIterator([100, 200, "Yong"]);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
/* 打印输出 */
/*
{ value: 100, done: false }
{ value: 200, done: false }
{ value: 'Yong', done: false }
{ value: undefined, done: true }*/
假如我们想要让普通的对象也能够直接支持(除了Object.keys()形式)for...of循环,那么可以考虑主动的在对象或者对象的原型对象上面部署iterator迭代器接口,下面简单给出对应的示例代码。
/* 方案-01 */
// let o = {
// name: "Yong",
// address: "GuangZhou",
// [Symbol.iterator]() {
// let idx = 0;
// let map = [];
// Object.keys(this).forEach(key => map.push([key, this[key]]))
// return {
// next: () => idx < map.length ? { value: map[idx++], done: false }
// : { value: undefined, done: true }
// };
// }
// };
/* 方案-02 */
Object.prototype[Symbol.iterator] = function() {
let idx = 0;
let map = [];
Object.keys(this).forEach(key => map.push([key, this[key]]))
return {
next: () => idx < map.length ? { value: map[idx++], done: false }
: { value: undefined, done: true }
};
}
let o = {
name: "Yong",
address: "GuangZhou"
};
/* 测试代码 */
let iterator = o[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log("+++++++++++++++++");
for (const iterator of o) {
console.log('iterator:', iterator);
}
console.log("+++++++++++++++++");
for (const [key, value] of o) {
console.log('key:', key, "val:", value);
}
/* 打印输出 */
/*
{ value: [ 'name', 'Yong' ], done: false }
{ value: [ 'address', 'GuangZhou' ], done: false }
{ value: undefined, done: true }
+++++++++++++++++
iterator: [ 'name', 'Yong' ]
iterator: [ 'address', 'GuangZhou' ]
+++++++++++++++++
key: name val: Yong
key: address val: GuangZhou */
如果想要更简单点,其实还可以借助 Generator 生成器函数来实现。
/* 生成器函数来实现 */
let obj = {
*[Symbol.iterator]() {
yield "H";
yield "e";
yield "l";
yield "l";
yield "o"
}
}
for (const e of obj) {
console.log('e:', e);
}
/* 打印输出 */
/*
e: H
e: e
e: l
e: l
e: o */
前端开发系列125-进阶篇之Iterator的更多相关文章
- openlayers5-webpack 入门开发系列一初探篇(附源码下载)
前言 openlayers5-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载 ...
- leaflet-webpack 入门开发系列一初探篇(附源码下载)
前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址 w ...
- 【Windows10 IoT开发系列】配置篇
原文:[Windows10 IoT开发系列]配置篇 Windows10 For IoT是Windows 10家族的一个新星,其针对不同平台拥有不同的版本.而其最重要的一个版本是运行在Raspberry ...
- ESP8266开发之旅 进阶篇② 闲聊Arduino IDE For ESP8266烧录配置
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- 【webpack 系列】进阶篇
本文将继续引入更多的 webpack 配置,建议先阅读[webpack 系列]基础篇的内容.如果发现文中有任何错误,请在评论区指正.本文所有代码都可在 github 找到. 打包多页应用 之前我们配置 ...
- iOS开发系列--Swift进阶
概述 上一篇文章<iOS开发系列--Swift语言>中对Swift的语法特点以及它和C.ObjC等其他语言的用法区别进行了介绍.当然,这只是Swift的入门基础,但是仅仅了解这些对于使用S ...
- 旨在脱离后端环境的前端开发套件 - IDT Server篇
IDT,一个基于Nodejs的,旨在脱离后端环境的前端开发套件,目的就是能让前端开发完全脱离后端的环境,无论后端是什么模板引擎(主流),都能应付自如. IDT主要包括两大部分:Server + Bui ...
- 前端开发【第2篇:CSS】
鸡血 样式的属性多达几千个,但别担心,按照80-20原则,常用的也就几十个,你完全可以掌握它. Css初识 HTML的诞生 早期只有HTML的时候为了让HTML更美观一点,当时页面的开发者会把颜色写到 ...
- [置顶]【实用 .NET Core开发系列】- 导航篇
前言 此系列从出发点来看,是 上个系列的续篇, 上个系列因为后面工作的原因,后面几篇没有写完,后来.NET Core出来之后,注意力就转移到了.NET Core上,所以再也就没有继续下去,此是原因之一 ...
- openlayers4 入门开发系列之风场图篇
前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...
随机推荐
- Unity Mask原理及自定义遮罩
主要内容 StencilBuffer是什么? 自定义Shader来实现遮罩 Unity Mask的原理 1.什么是StencilBuffer GPU在渲染前会为每个像素点分配一个1字节(8位)大小的内 ...
- JMeter跨线程传参总结
- Issue: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'
问题: Fatal error compiling: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImpor ...
- require、include、require_once和use的区别/命名空间的使用方法
1.require.include require.include都是文件包含,不同的是require语句会输出错误信息,并且立即终止脚本处理.而include语句在没有找到文件时则会输出警告,不会终 ...
- mousedown mouseenter mouseup firefox,还是通一用webkit吧,细节的坑刚刚填,毕竟现在是webkit一家大拿!
mouse,mouseup,mouseenter,mouseover,click坑呀,浏览器表现居然不一致: firefox呀 直接上代码吧,自定义个el-table的select,chrome表现正 ...
- Codeforces Round 978 (Div. 2) A-D1 题解
A. Bus to Pénjamo 题意 有一辆车上面有 \(r\) 排座位,每排有 \(2\) 个座位,现在共 \(n\) 个家庭出行坐公交车,每个家庭 \(a_i\) 个人(保证 \(2r\ge ...
- idea中代码提交流程(git版)
本文主要分享一下如何通过idea通过git拉取项目并且进行编辑后提交到远程master上进行合并. 1.安装idea编译器,我们用的是社区版本2021.1,安装步骤略过. 2.打开idea,点击Fil ...
- TVM: 编译深度学习模型的快速入门教程
支持的TVM硬件后端概述 下图显示了 TVM 目前支持的硬件后端: 在本教程中,将选择 cuda 和 llvm 作为目标后端.首先,让导入 Relay 和 TVM. import numpy as n ...
- My Calendar III——LeetCode⑪
//原题链接https://leetcode.com/problems/my-calendar-iii/submissions/ 题目描述 Implement a MyCalendarThree cl ...
- UML类图-UML Class Diagram
.wj_nav { display: inline-block; width: 100%; margin-top: 0; margin-bottom: 0.375rem } .wj_nav_1 { p ...