郑成事

导航

javascriptRemke之深入迭代

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

 

  

posted on 2021-10-09 21:24  郑成事  阅读(52)  评论(0编辑  收藏  举报