ES6入门十:iterator迭代器
- 迭代模式
 - ES6迭代器标准化接口
 - 迭代循环
 - 自定义迭代器
 - 迭代器消耗
 
一、迭代模式
迭代模式中,通常有一个包含某种数据集合的对象。该数据可能存在一个复杂数据结构内部,而要提供一种简单的方法能够访问数据结构中每个元素。对象消费者并不需要知道如何组织数据,所有需要做的就是取出单个数据进行工作。
迭代模式API的设计:通常会设置一个next()方法来获取每个元素;为了方便操作,会设置一个hasNext()方法或者done属性来判断是否已经到达数据的末尾;
基于上面的简单设计思想,假设对象名为agg,可以在类似下面这两个示例一样迭代消费对象agg的数据:
//一:
var element;
while(element = agg.next()){
console.log(element);
}
//二:
while(agg.hasNext()){//或者agg.done
console.log(agg.next());
}
下面使用普通的数组作为agg的数据结构,来模拟实现一个迭代器:
 var agg = (function(){
     var index = 0;
     var data = [1,2,3,4,5,];
     var length = data.length;
     return {
         next:function(){
             var element;
             if(!this.hasNext()){
                 return null;
             }
             element = data[index];
             index ++;
             return element;
         },
         hasNext:function(){
             return index < length;
         }
     };
 }());
 while(agg.hasNext()){
     console.log(agg.next());
 }
为了实现迭代器多次迭代数据的能力,还可以提供额外的rewind()方法来实现重置迭代器;另外可能出现不前进指针返回当前元素的情况,可以设置current()方法来实现:
 var agg = (function(){
     var index = 0;
     var data = [1,2,3,4,5,];
     var length = data.length;
     return {
         next:function(){ //迭代数据
             var element;
             if(!this.hasNext()){
                 return null;
             }
             element = data[index];
             index ++;
             return element;
         },
         hasNext:function(){ //判断迭代器是否到达数据的末尾
             return index < length;
         },
         rewind:function(){ //重置迭代器
             index = 0;
         },
         current:function(){ //不前进指针返回当前元素
             return data[index];
         }
     };
 }());
二、ES6迭代器标准化接口
~ES6迭代器next()接口(必须API):Iterator [required]
next(){method}:取得下一个IteratorResult
~ES6迭代器支持的扩展接口(可选API)成员:Iterator [optional]
return() {method}:停止迭代器并返回IteratorResult
throw() {method}:报错并返回IteratorResult
~IteratorResult接口指令:IteratorResult
value {property}:当前迭代值或者最终返回值(如果undefined为可选的)
done {property}:布尔值,指示完成状态
~ES6迭代器还为必须提供生成器的需求,提供一个生成器对象的接口Iterable:
@@iterator() {method}:产生一个Iterator
@@iterator是一个特殊的内置符号,表示可以为这个对象产生迭代器的方法。
IteratorResult接口指定了从任何迭代器操作返回的值必须是下面这种形式的对象:
{ value: .. , done: true/false }
内置迭代器总是返回这种形式的值,如果有需求返回值还可以有更多属性;JavaScript不支持任何“接口”的概念,所以代码符合规范指示单纯的惯用法,但是JavaScript期望迭代器的指令(如for..of循环)所提供的东西必须符合这些接口,否在代码会失败。
三、迭代循环
在ES6中,新增了一个循环指令for..of来实现数据迭代,在ES6之前就有for、for..in、还有Array原型上的forEach方法都可以实现遍历,为什么还要新增for..of呢?for..of与迭代器又有什么关系呢?
for循环指令是根据自定义的条件来实现循环执行,并不一定用来遍历数据,这两种情况都不符合迭代模式,for只是一个简单的循环代码结构,但也可以用来遍历迭代数据。
for..in用来遍历可枚举对象属性,是基于对象属性特性实现的一个数据遍历的代码结构,返回数据名称,严格来说for..in是实现了枚举。因为根据对象属性枚举特性实现导致其可能不能完整迭代数据或者迭代不需要的数据(受属性特性限制),这也不符合迭代模式迭代返回数据有迭代接口实现的规范。
forEach是一个不完整的简单迭代器,其不能for..of一样使用break或者return fales一样中断迭代过程,也就是说缺少return接口。
 var arr = [3, 5, 7];
 arr.forEach(function (value) {
   console.log(value);
   if (value == 5) {
     return false; //这里并不能中断迭代器
   }
 });
 //3 5 7
但是for..of只能迭代带有迭代接口的数据,比如Array,Set,Map,Nodelist对象,因为这些数据类型原型都带有默认的迭代函数Symbol.iterator,所以对于普通的数据集可以根据ES6的迭代接口规范自定义迭代器来实现数据迭代。
如果阅读过我的上一篇博客ES6入门九:Symbol元编程会了解到Symbol对象上有一个静态属性iterator,这个属性就是用来实现自定义迭代接口的标识,被for..of执行迭代操作时会自动根据对象上的[Symbol.iterator]属性获取数据上的迭代接口,下面展示之前的自定义数据集及其迭代接口的实现方法:
 var obj = {
     0:"a",
     1:"b",
     2:"c",
     length:3,
     [Symbol.iterator]:function(){
         let curIndex = 0;
         let next = () => {
             return {
                 value:this[curIndex],
                 done:this.length == curIndex++,
             }
         }
         return {
             next
         }
     }
 }
 for(let p of obj){
     console.log(p); //输出a b c (如果不再obj上添加Symbol.iterator迭代方法,会报错:obj is not iterable)
 }
四、自定义迭代器
在第三节的最后一个示例中演示了一个带有迭代器的对象obj,实现了ES6中的next()、IteratorResult的{value,done}接口,但还缺少Iterator [optional]的{return,throw}及Iterable的@@iterator()。基于这个不完善的数据集对象,实现一个带有完整的迭代器数据集对象:
 var obj = {
     0:"a",
     1:"b",
     2:"c",
     length:3,
     [Symbol.iterator]:function(){
         let curIndex = 0;
         let next = () => {
             return {
                 value:this[curIndex],
                 done:this.length == curIndex++,
             }
         };
         let returnFn = (v) =>{//在for..of迭代循环中使用break中断循环迭代会触发,可以使用迭代对象触发这个方法,根据具体业务需求实现
             curIndex = this.length;
             return {value:v,done:true};//这个返回值没有实际意义,v的值为undefined,done也不会作用到迭代器上的遍历
         }
         return {
             [Symbol.iterator](){return this},//返回迭代器Iterator本身,for..of迭代循环就是基于传入对象的的[Symbol.iterator]
             next,
             return:returnFn,
             throw(e){ return new Error(e); }//这个方法暂时没有测试
         }
     }
 }
测试代码1:
var it = obj[Symbol.iterator]();
for(let p of it){//for..of能使用it作为迭代对象得益于示例中第19行代码,该代码后面有具体解析
console.log(p); //打印:a b
if(p == "b"){
break; //这里会触发迭代器的return方法
}
}
it.next();//{value: undefined, done: true},触发了return方法修改遍历器位置为最末尾,不能再迭代
测试代码2:
var it = obj[Symbol.iterator]();
console.log(it.next().value);//a 因为示例中的第19行代码的实现,可以实现单步迭代,还能在这个迭代器后面继续使用for..of继续迭代it
for(let p of it){
console.log(p);//b c
}
1.构造一个迭代器来生产一个无限斐波那契序列:
 var Fib = {
     [Symbol.iterator](){
         var n1 = 1, n2 = 1;
         return {
             [Symbol.iterator](){ return this; },
             next(){
                 var current = n2;
                 n2 = n1;
                 n1 = n1 + current;
                 return { value:current, done:false };
             },
             return(v){
                 console.log("Fibonacci sequence abandoned.");
                 return {value:v, done:true };
             }
         };
     }
 };
 for (var v of Fib){
     console.log(v);
     if( v > 50 ) break;
 }
 //1 1 3 5 8 13 21 34 55
 //Fibonacci sequence abandoned.
2.基于迭代器实现一个队列:
 var tasks = {
     [Symbol.iterator](){
         var arr = this.actions;
         return {
             [Symbol.iterator](){ return this; },
             next(...args){
                 if(arr.length > 0){
                     let res = arr.shift()(...args);
                     return {value: res, done:false}
                 }else{
                     return {value:undefined, done: true}
                 }
             },
             return(v){
                 arr.length = 0;
                 return {value:v, done:true};
             }
         };
     },
     actions:[],
     add(...funs){
         this.actions.push(...funs);
     }
 };
 function fn1(x){
     console.log("step 1:",x);
     return x * 2;
 }
 function fn2(x,y){
     console.log("step 2:",x,y);
     return x + (y *2);
 }
 function fn3(x,y,z){
     console.log("step 3:",x,y,z);
     return (x * y) + z;
 }
 tasks.add(fn1,fn2,fn3);
 var it = tasks[Symbol.iterator]();
 console.log(it.next(10).value);//step 1: 10  ------ 20
 console.log(it.next(20,50).value);//step 2: 20 50 --------  120
 console.log(it.next(20,50,120).value);//step 3: 20 50 120 --------  1120
 console.log(it.next().value);//undefined
3.在Number类原型上定义一个迭代器来表示单个数上的元操作,默认范围是从0到n,这样可以实现一些需要批量的有序数值生成,而且可以通过for..of当个操作数值,或者通过扩展符[...n]来收集这组数值:
 if(!Number.prototype[Symbol.iterator]){
     Object.defineProperty(
         Number.prototype,
         Symbol.iterator,
         {
             writable:true,    //可重写
             configurable:true,//可配置
             enumerable:false, //不可枚举
             value:function iterator(){
                 var i = 0, inc, done = false, top = +this;
                 inc = this > 0 ? 1 : -1;
                 return {
                     [Symbol.iterator](){return this;},
                     next(){
                         inc = i;
                         if(top >= 0 ){
                             i++;
                         }else{
                             i--;
                         }
                         if( Math.abs(inc) > Math.abs(top) ){
                             done = true;
                         }
                         return {value:inc, done:done}
                     },
                     return(v){
                         done = true;
                         return {value:v,done:done}
                     }
                 }
             }
         }
     );
 }
 for(let n of 5){
     console.log(n); //0 1 2 3 4 5
 }
 console.log([...-5]);//[0, -1, -2, -3, -4, -5]
五、迭代器消耗
在第三节中提到过JavaScript原生类自带迭代的有Array,Set,Map,Nodelist,其实还有一个类也自带了迭代器,这个类就是arguments。
Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]
除了前面一直作为示例演示的for..of基于迭代器实现,并一次性消耗数据集的所有数据,还有解构(...)数据操作也是基于迭代器实现,并且也同样是一次性消耗数据集所有数据。但是for..of的break可以触发return方法中断迭代器。
迭代器也可以通过手动调用数据集的[Symbol.iterator]()方法获得迭代器,然后以单个数据元的方式消耗迭代器。中途同样可以启动return()方法取消迭代器,如果对这些有疑问的话可以回到前面的示例代码,仔细阅读迭代器API的具体实现就能清晰的了解它为什么可以做得到。
ES6入门十:iterator迭代器的更多相关文章
- ES6入门十二:Module(模块化)
		
webpack4打包配置babel7转码ES6 Module语法与API的使用 import() Module加载实现原理 Commonjs规范的模块与ES6模块的差异 ES6模块与Nodejs模块相 ...
 - ES6入门之Iterator和for...of
		
Iterator遍历器 遍历器(Iterator)就是这样一种机制.它是一种接口,为各种不同的数据结构提供统一的访问机制.任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据 ...
 - ES6入门十一:Generator生成器、async+await、Promisify
		
生成器的基本使用 生成器 + Promise async+await Promise化之Promisify工具方法 一.生成器的基本使用 在介绍生成器的使用之前,可以简单理解生成器实质上生成的就是一个 ...
 - ES6笔记(6)-- Set、Map结构和Iterator迭代器
		
系列文章 -- ES6笔记系列 搞ES6的人也是够无聊,把JS弄得越来越像Java.C++,连Iterator迭代器.Set集合.Map结构都出来了,不知道说什么好... 一.简单使用 1. iter ...
 - 设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型)
		
设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型) 1.概述 类中的面向对象编程封装应用逻辑.类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态.单独的对象是一种组织代码的 ...
 - 设计模式(十五):Iterator迭代器模式 --  行为型模式
		
1.概述 类中的面向对象编程封装应用逻辑.类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态.单独的对象是一种组织代码的有用方法,但通常你会处理一组对象或者集合. 集合不一定是均一的.图形用 ...
 - C#设计模式之十六迭代器模式(Iterator Pattern)【行为型】
		
一.引言 今天我们开始讲"行为型"设计模式的第三个模式,该模式是[迭代器模式],英文名称是:Iterator Pattern.还是老套路,先从名字上来看看."迭代器模 ...
 - C#设计模式之十五迭代器模式(Iterator Pattern)【行为型】
		
一.引言 今天我们开始讲“行为型”设计模式的第三个模式,该模式是[迭代器模式],英文名称是:Iterator Pattern.还是老套路,先从名字上来看看.“迭代器模式”我第一次看到这个名称,我的理解 ...
 - ES6入门笔记
		
ES6入门笔记 02 Let&Const.md 增加了块级作用域. 常量 避免了变量提升 03 变量的解构赋值.md var [a, b, c] = [1, 2, 3]; var [[a,d] ...
 
随机推荐
- linux的最简socket编程
			
一.背景 好久没有进行linux下的socket编程了,复习一下 二.服务端完整代码 #include <stdio.h> #include <stdlib.h> #inclu ...
 - Android 显示系统:飞思卡尔平台图形界面与GPU硬件加速
			
图形是Android平台中的一个大主题,包含java/jni图形框架和2d/3d图形引擎(skia.OpenGL-ES.renderscript). 本文档描述了飞思卡尔设备上的一般Android图形 ...
 - kotlin之操作符重载
			
一元操作符 表达式 对应的函数 +a a.unaryPlus() -a a.unaryMinus() !a a.not() a++ a.inc() a-- a.dec() fun main(arg: ...
 - android studio 低版本升级高版本的问题
			
配置 适用场景 2.0 升级3.0 / 3.0升级3.1 gradle的问题注意每个AS版本的gradle插件都对应了gradle的版本 传送门 https://developer.android. ...
 - CentOS查看每个进程的网络流量
			
所需工具nethogs 安装:yum install -y nethogs 使用:nethogs eth0 sudo nethogs -s //按接收流量大小排序 如上图,PID一列就是进程的PID, ...
 - 知识点整理-网络IO知识总结
			
UNIX 系统下的 I/O 模型有 5 种 同步阻塞 I/O.同步非阻塞 I/O.I/O 多路复用.信号驱动 I/O 和异步 I/O 什么是I/O 所谓的I/O 就是计算机内存与外部设备之间拷贝数据的 ...
 - 阅读随笔 Spring、Mybatis
			
一.<Spring+Mybatis 企业应用实战>(第2版本) 本书讲解了Spring.Mybatis及Spring+MyBatis 工作中的常用方法,没有太深入的原理性讲解,介绍 “如何 ...
 - UPDATE SELECT OUTPUT
			
-- 定义临时表变量,用于 output into 使用 DECLARE @VarOrderStatus table ( OrderNo nvarchar(50) NULL) -- update 表U ...
 - 模板引擎doT.js用法详解
			
作为一名前端攻城师,经常会遇到从后台ajax拉取数据再显示在页面的情境,一开始我们都是从后台拉取再用字符串拼接的方式去更达到数据显示在页面! <!-- 显示区域 --> <div i ...
 - windows下使用命令行编译、链接C++源文件
			
目录 1.流程 2.操作 1.流程 .cpp-->.o-->.exe 分别为 源文件-->中间目标文件-->可执行文件 两个-->的过程分别为编译.链接 p.s.多个 . ...