javascriptRemke之深入迭代

前言:"迭代"意为按照顺序反复多次执行一段程序,ECMAscript6中新增了两个高级特性:迭代器与生成器,使用这两个特性能更高效地实现迭代,本文主要围绕迭代器详细展开叙述。

一、何为迭代

  迭代即重复执行某段程序,最简单的一种迭代即计数循环。

1 for(let i =0;i<10;i++){
2 console.log(i);
3 }

  迭代会在一个有序集合上进行,数组是JavaScript中最经典的例子,数组每一项可以通过索引获取,所以整个数组都可以通过递增索引来遍历,但是很多时候使用数组进行循环并不理想。原因如下:

  • 迭代之前需要事先知道如何使用数据结构。在前后端实际开发中并不能完全适用于所有数据结构,前端通常得根据后端得接口文档进行数据处理,数组的遍历有时并不能满足需求。
  • 遍历顺序可能非数据结构固有的。通过递增索引来访问数据是特定于数组类型的方式,并不适用于其他隐式顺序的数据结构。

  

  ES5中为实现通用迭代需求,新增了Array.prototype.forEach()方法,但是效果仍不理想。这个方法实现了单独记录索引(传入forEach作为参数的回调函数的第一个参数)并能通过数组对象取得值,但是仍未解决标识迭代何时中止的问题,因此这个方法也只适用于数组,且回调形式较为繁琐。因此,ES6中采用迭代器模式作为解决方案,让开发者无须实现知道如何迭代就能实现迭代操作。

1 let arr = ['sunwukong','zhubajie','shaheshang'];
2 arr.forEach((value,index,obj)=>{
3 console.log(value)
4 })
5 //sunwukong
6 //zhubajie
7 //shaheshang

二、迭代器模式

2.1  可迭代对象

  迭代器模式描述了一个方案,把具有迭代能力的结构称为"可迭代对象"。可迭代对象可以理解为数组或集合(Set)类型的对象。它们拥有以下特性:

  • 包含有限个元素
  • 具有无歧义的遍历顺序

  计数循环和数组都具有可迭代对象的行为。

2.2  迭代器

  迭代器模式中还有一个关键概念"迭代器",迭代器是按需创建的一次性对象,每个迭代器都会关联一个可迭代对象,且迭代器会暴露可迭代对象的相关API。

  当使用可以接收可迭代对象的原生语言(for-of、Map、Set...)时,会创建一个迭代器,从而实现迭代。

  *迭代器模式将可迭代对象与迭代器分离,实现了迭代器无须了解与之关联的可迭代对象的结构,只需要知道如何取得连续的值,这也正式分离可迭代对象与迭代器的强大之处。

2.3  可迭代协议

  实现可迭代协议要求具备两种能力:

  • 支持迭代的自我识别能力
  • 创建接口的能力

  要创建接口,就必须要暴露一个属性作为默认迭代器且这个属性必须要用特殊的Symbol.iterator作为建。默认迭代器属性必须引用一个迭代器工厂函数,通过调用这个工厂函数返回一个新的迭代器,简单来说,只有当对象拥有Symbol.iterator这一属性,这一对象才可以实现迭代

1 let str = 'abc';
2 console.log(str[Symbol.iterator]());// f value(){native code};
3 let obj = {};
4 console.log(obj[Symbol.iterator]());//报错
5 console.log(obj.__proto__.hasOwnProperty(Symbol.iterator));
6 //false
7 console.log(arr.__proto__.hasOwnProperty(Symbol.iterator));
8 //true

  能够实现接口功能的内置属性有以下几类:

  • 字符串
  • 数组
  • 映射(Map)
  • 集合(Set)
  • arguments对象(伪数组)
  • NodeList等DOM集合(document.queryselectorAll()、document.getElementsByClassName()...)

  能够接收可迭代对象的原始语言包括:

  • for-of
  • 数组循环
  • 扩展运算符
  • Array.from()
  • 创建集合
  • 创建映射
  • Promise.all()
  • Promise.race()
  • yield*操作符

  这些原始语言会在后台调用提供的可迭代对象的这个工厂函数,从而创建一个迭代器。

三、迭代器协议

  迭代器是一种一次性使用的对象,在使用能够接收可迭代对象的原始语言时会临时创建,用于迭代与其关联的可迭代对象。

  迭代器API使用next()方法在可迭代对象中遍历数据,每次成功调用next(),都会返回一个IteratorResult对象,这一对象包含迭代器返回的下一个值(value)和状态(done)。若不调用next()方法则无法知道迭代器的当前位置,因此next()是迭代器中必须存在的方法,不然仍然无法成为迭代器。

  next()返回对象包含两个属性:done和value,done是一个布尔值,表示next()是否还可以再次调用并取得下一个有效值,为false表示状态仍未耗尽,还可以获取下一有效值,为true时则下次迭代返回的value为undefined。value包含可迭代对象的下一个值。

 1 //可迭代对象
2 let arr = ['Billy','Jack'];
3 //迭代器工厂函数
4 console.log(arr[Symbol.iterator]); //f values(){native code};
5 //创建一个迭代器实例
6 let iterator = arr[Symbol.iterator()]();
7 console.log(iterator) //ArrayIterator();
8 //执行迭代
9 console.log(iterator.next()); //{done:false,value:'Billy'};
10 console.log(iterator.next()); //{done:false,value:'Jack'};
11 console.log(iterator.next()); //{done:true,value:undefined};

  注意:

  •   不同迭代器实例相互之间没有关系,只会独立遍历可迭代对象。
  •   迭代器并不与可迭代对象某个时刻的快照绑定。即中途插入新元素,使用next()进行迭代会展现新元素。

四、自定义迭代器

  任何使用Iterator接口的对象都可以作为迭代器使用。

  下方通过实现Iterator接口完成自定义迭代器。

 

 1 class IteratorTest{
2 constructor(limit){
3 this.limit = limit;
4 this.present = 1;
5 }
6 [Symbol.iterator](){
7 return this;
8 }
9 next(){
10 if(this.present<this.limit){
11 return{
12 done:false,value:this.present++
13 }
14 }
15 else{
16 return{
17 done:true,value:undefined
18 }
19 }
20 }
21 }
22 const iterator = new IteratorTest(5)
23 for (let item of iterator) {
24 console.log(item)
25 }
26 //1
27 //2
28 //3
29 //4

  上方例子在for-of的作用下调用Symbol.iterator属性返回自身实例(创建迭代器),接着调用实例原型上的next()方法实现迭代。

  但是上方例子存在的缺陷为每个实例之恩被迭代一次。若多次遍历不打印结果。

  为了能让可迭代对象能够实现正常迭代需求,因此每创建一个迭代器就对应一个新迭代器,采用的解决方案为将计数变量放在闭包里,然后通过闭包返回迭代器。

 1 class IteratorTest{
2 constructor(limit){
3 this.limit = limit;
4 }
5 [Symbol.iterator](){
6 let start = 1;
7 let present = start;
8 let limit = this.limit;
9 return{
10 next(){
11 if(present<limit){
12 return{
13 done:false,value:present++
14 }
15 }
16 else{
17 return{
18 done:true,value:undefined
19 }
20 }
21 }
22 }
23 }
24 }
25 const iterator = new IteratorTest(5)
26 for (let item of iterator) {
27 console.log(item)
28 }
29 for (let item of iterator) {
30 console.log(item)
31 }
32 //1
33 //2
34 //3
35 //4
36 //1
37 //2
38 //3
39 //4

  上面例子因为每个迭代器都实现了Iterator接口且都有next()方法,所以在任何期待可迭代对象的地方都可以循环。

五、提前中止迭代器

  为提前中止迭代器,需要return()方法返回一个用于提前中止迭代的有效对象(IteratorResult),这个对象可以只返回{done:true}

  return()方法用于指定迭代器在提前关闭时执行的逻辑。提前关闭迭代器的可能情况为:

  •   for-of循环通过break、continue、return或throw提前退出。
  •   解构操作并未消费所有值。

 

 1 class IteratorTest{
2 constructor(limit){
3 this.limit = limit;
4 }
5 [Symbol.iterator](){
6 let start = 1;
7 let present = start;
8 let limit = this.limit;
9 return{
10 next(){
11 if(present<limit){
12 return{
13 done:false,value:present++
14 }
15 }
16 else{
17 return{
18 done:true,value:undefined
19 }
20 }
21 },
22 return(){
23 console.log('exit');
24 return {done:true};
25 }
26 }
27 }
28 }
29 const iterator = new IteratorTest(5)
30 for (let item of iterator) {
31 if(item>2){
32 break;
33 }
34 console.log(item);
35 }
36 //1
37 //2
38 //exit

  注意:如果迭代器没有关闭,则还可以继续从上次离开的地方继续迭代。

 1 let arr = [1,2,3,4,5];
2 const iterator2 = arr[Symbol.iterator]();
3 for(let i of iterator2){
4 console.log(i);
5 if(i>2){
6 break
7 }
8 }
9 //1
10 //2
11 //3
12 for(let i of iterator2){
13 console.log(i);
14 }
15 //4
16 //5

  

javascriptRemke之深入迭代的更多相关文章

  1. javascript中的Array对象 —— 数组的合并、转换、迭代、排序、堆栈

    Array 是javascript中经常用到的数据类型.javascript 的数组其他语言中数组的最大的区别是其每个数组项都可以保存任何类型的数据.本文主要讨论javascript中数组的声明.转换 ...

  2. IteratorPattern(迭代子模式)

    /** * 迭代子模式 * @author TMAC-J * 聚合:某一类对象的集合 * 迭代:行为方式,用来处理聚合 * 是一种行为模式,用于将聚合本身和操作聚合的行为分离 * Java中的COLL ...

  3. JavaScript高级程序设计--对象,数组(栈方法,队列方法,重排序方法,迭代方法)

    1.使用对象字面量定义对象 var person={}; 使用这种方式创建对象时,实际上不会调用Object构造函数. 开发人员更喜欢对象字面量的语法.   2.有时候需要传递大量可选参数的情形时,一 ...

  4. python征程3.1(列表,迭代,函数,dic,set,的简单应用)

    1.列表的切片. 1.对list进行切片.'''name=["wangshuai","wangchuan","wangjingliang", ...

  5. 让产品有效迭代,前端A/B Testing的简单实现

    A/B Testing简介 互联网产品的迭代速度很快,往往一周一小发布,一月一大发布,产品提出的种种需求,哪些改动是提升产品体验的,哪些是阻碍产品进步的,如果没有数据可以参考,仅仅是靠拍脑袋的话,对产 ...

  6. JAVA中的for-each循环与迭代

    在学习java中的collection时注意到,collection层次的根接口Collection实现了Iterable<T>接口(位于java.lang包中),实现这个接口允许对象成为 ...

  7. 迭代字典中的key和value

    字典是python中十分重要的一个内容. 今天我们来谈谈,在一个 for 循环中,能否同时迭代 key和value?当然可以咯. dict 对象的 items() 方法返回的值: >>&g ...

  8. python中的迭代、生成器等等

    本人对编程语言实在是一窍不通啊...今天看了廖雪峰老师的关于迭代,迭代器,生成器,递归等等,word天,这都什么跟什么啊... 1.关于迭代 如果给定一个list或tuple,我们可以通过for循环来 ...

  9. ICP算法(Iterative Closest Point迭代最近点算法)

    标签: 图像匹配ICP算法机器视觉 2015-12-01 21:09 2217人阅读 评论(0) 收藏 举报 分类: Computer Vision(27) 版权声明:本文为博主原创文章,未经博主允许 ...

随机推荐

  1. 在按照ROS官方步骤操作,同时用Git管理整个过程,git clone的新catkin_ws报错: catkin_package() include dir 'include' does not exist relative to

    在按照ROS官方步骤操作,同时用Git管理整个过程,git clone的新catkin_ws报错如下: CMake Error at /opt/ros/kinetic/share/catkin/cma ...

  2. k8s笔记0528-基于KUBERNETES构建企业容器云手动部署集群记录-6

    1.创建一个测试用的deployment [root@linux-node1 ~]# kubectl run net-test --image=alpine --replicas=2 sleep 36 ...

  3. go语言文件系统

    检测文件是否存在 //存在返回 true,不存在返回 false func fileIfExist(filename string) bool { _, err := os.Stat(filename ...

  4. python 文件批量改名重命名 rename

    path = '/Volumes/Seagate/dev/imgs/' os.chdir(path) print('cwd: ', os.getcwd()) for f in os.listdir(' ...

  5. https(ssl) 免费证书

    https://letsencrypt.org/getting-started/ https://certbot.eff.org/lets-encrypt/centosrhel7-nginx http ...

  6. 七、Abp vNext 基础篇丨文章聚合功能下

    介绍 不好意思这篇文章应该早点更新的,这几天在忙CICD的东西没顾得上,等后面整好了CICD我也发2篇文章讲讲,咱们进入正题,这一章来补全剩下的 2个接口和将文章聚合进行完善. 开工 上一章大部分业务 ...

  7. 关于 antd tree 组件的推拽操作

    最近项目中使用到 tree 组件的推拽操作, 按常理来说应该主要用到其中的 onDrop 事件,但其中的参数又没有详细的说明,只是在官网给了个例子,网上搜索后又没有发现到位的总结. 因此经过N次的测试 ...

  8. Django的form组件基本使用——简单校验

    from django.contrib import admin from django.urls import path from app01 import views urlpatterns = ...

  9. shell中的引号

    单引号: 所见即所得 原封不动输出 双引号: 与单引号类似 特殊符号进行解析 ( $ $() `` ! ) 无引号: 与双引号类似 支持通配符( {} * ) 反引号: 优先执行 优先执行里面的命令, ...

  10. php-fpm进程数控制

    一.名词解释 CGI是Common Gateway Interface(通用网管协议),用于让交互程序和Web服务器通信的协议.负责处理URL的请求,启动一个进程,将客户端发送的数据作为输入,有Web ...