前端开发系列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 官网在线例子 ...
随机推荐
- 探秘Transformer系列之(26)--- KV Cache优化---分离or合并
探秘Transformer系列之(26)--- KV Cache优化 之 PD分离or合并 目录 探秘Transformer系列之(26)--- KV Cache优化 之 PD分离or合并 0x00 ...
- 🎀Java-Exception与RuntimeException
简介 Exception Exception 类是所有非致命性异常的基类.这些异常通常是由于编程逻辑问题或外部因素(如文件不存在.网络连接失败等)导致的,可以通过适当的编程手段来恢复或处理.Excep ...
- OAuth2.0 学习
- Cobalt Strike基础
Cobalt Strike基础 Staged(有阶段) 在有阶段的执行方式中,分为Stager和Stage两个阶段 Stager(初始执行载荷): 定义:Stager是Stage 1,是一个较小的 ...
- 使用python批量爬取wallhaven.cc壁纸站壁纸
偶然发现https://wallhaven.cc/这个壁纸站的壁纸还不错,尤其是更新比较频繁,用python写个脚本爬取 点latest,按照更新先后排序,获得新地址,发现地址是分页展示的,每一页24 ...
- 3d xna fbx winfrom 读取
本文通过参考网上资源做的一个例子. 本程序的功能就是通过xna 将3d 图像显示到winfrom 对他进行旋转操作. 首先我们先准备好两个文件夹 model 文件夹放fbx文件,textures 放 ...
- 【工具】Vscode翻译插件推荐(不用谷歌翻译api、支持短句英汉互译、支持查词、支持自动补全、不需要浏览器)
2024/04/24说明:这篇暂时修改为粉丝可见,因为正在冲粉丝量,等到我弄完了粉丝量的要求,我就改回来!不方便看到全文的小伙伴不好意思!! 需求: 1)偶尔需要查英文生词: 2)有时候想不起来中文对 ...
- 运筹学之"名词解释"
1.转移概率 转移概率是指某个销售者保持,获得或失去消费者的概率 2.阶石法中的改进指数 阶石法中的改进指数是指循着改进路线,当货物的运输量作为一个单位发生变化时,会引起总运输费用的改变 3.相关关系 ...
- golang map 和 interface 的一些记录
golang的map读取是不需要判断key是否存在的,不存在的key会返回默认值. 如果map的value是interface,那么interface是需要先进行类型转换的,非要求类型的转换,得到结果 ...
- Beautiful code and beautiful life
You may ask me why do i strive constantly, what i am striving for? Yep, the same question haunts me ...