Iterator遍歷器基礎

一. 遍歷器Iterator

1. 基本概念

  • js原有的表示"集合"的數據結構,主要是數組和對象。ES6添加了Map和Set。
  • 這樣就有了四種數據結構能夠用於描述集合。可是須要一種接口體制來處理全部不一樣的數據結構
  • 遍歷器Iterator就是一種機制,是一種接口,能夠爲不一樣的數據結構提供統一的訪問機制
  • 任何數據結構只要部署了Iterator接口,就能夠完成遍歷操做(依次處理該數據結構的全部成員)。
  • Iterator接口的做用有三個:
  1. 爲各類集合形式的數據結構提供了一個統一的,簡便的訪問接口
  2. 使得數據結構的成員可以按照某種次序排序
  3. ES6創造了一種新的遍歷方式for...of循環Iterator接口主要就是供for...of循環使用的!

2.遍歷方法

  • Iterator的遍歷過程是建立一個指針對象,而後每次調用對象的next方法,返回當前成員的值
  • 返回的是一個對象,對象有兩個屬性,value和done兩個屬性。value是當前成員的值,done是一個布爾值,表示遍歷是否結束
  • 另外done:false和vallue:undefined是能夠省略的,由於不返回done屬性那麼確定就是undefined被判斷爲false,不返回value屬性那麼確定就是undefined
  • 下面是一種形式的Iterator接口的實現:
  • 須要注意的是,在原生的iterator接口實現中,對象是不能夠實現iterator接口的,也就是for...of循環會失敗
// 1. 對於對象來講,只能遍歷key爲索引的鍵值
 function makeIterator(obj){  var index=0;  return {  next:function(){  return {  done:obj[index]?true:false,  value:obj[index++]  }  }  }  }  var obj={0:'w',1:444,5:555,a:'yy'}  var res1=makeIterator(obj)  console.log(res1.next());//{done: true, value: "w"}  console.log(res1.next());//{done: true, value: 444}  console.log(res1.next());//{done: false, value: undefined}  console.log(res1.next());//{done: false, value: undefined}  // 而且注意:索引爲數字也不必定能遍歷到,由於要根據index的值,若是要遍歷到5,那麼須要繼續調用兩次next   // 1.2 可是不重寫iterator接口(如上使用函數,或者重寫Symbol.iterator接口)的話  // 對 對象使用for..of循環會報錯!  for(var item of {0:1,1:22}){  console.log(item);//Uncaught TypeError: {(intermediate value)(intermediate value)} is not iterable  }   // 2. 對於數組來講  function makeArray(arr){  var index=0;  return {  next:function(){  return {  done:arr[index]?true:false,  value:arr[index++]  }  }  }  }  var res2=makeArray([5,4,3])  console.log(res2.next())//{done: true, value: 5}  console.log(res2.next())//{done: true, value: 4}  console.log(res2.next())//{done: true, value: 3}  console.log(res2.next())//{done: false, value: undefined} 複製代碼

二. 默認Iterator接口

1. 基本概念

  • Iterator接口的目的即便爲全部的數據結構,提供統一的訪問機制,即for...of循環
  • 當使用for...of循環去遍歷某種數據結構時,該循環會自動去尋找Iterator接口
  • 一種數據結構只要部署了Iterator接口,那麼這種數據結構就是可遍歷的iterable
  • 而ES規定,默認的Iterator接口部署在Symbol.iterator屬性上,因此咱們能夠經過判斷是否具備Symbol.iterator屬性做爲可遍歷的依據
  • 通常獲取Symbol.iterator屬性都是使用[Symbol.iterator]的形式,由於Symbol.iterator至關於一個表示式,相似[1+'2']使用[]形式
// 1. 數組
 var arr=[]  console.log(arr[(Symbol.iterator)])//ƒ values() { [native code] }  // 2. Map  var map=new Map()  console.log(map[Symbol.iterator]);//ƒ entries() { [native code] }  // 3. Set  var set=new Set()  console.log(set[Symbol.iterator]);//ƒ values() { [native code] }  // 4. 對象(沒有iterator接口)  var obj={}  console.log(obj[Symbol.iterator]);//undeined  // 5. 對象添加數字屬性仍是沒有terator接口的,除非直接修改對象的Symbol.iterator接口  var obj2={0:1,1:222}  console.log(obj2[Symbol.iterator]);//undeined  // 6. 字符串也有iterator接口  var str="hello world"  console.log(str[Symbol.iterator]);//ƒ [Symbol.iterator]() { [native code] }  // 7. 數字沒有  var nums=1234  console.log(nums[Symbol.iterator]);//undeined  // 8. arguments,函數參數數組有!  function func(){  console.log(arguments[Symbol.iterator]);//ƒ values() { [native code] }  }  func(1,4,8)  // 9. Nodeslist 節點列表有!  console.log(document.getElementsByClassName("one")[Symbol.iterator]);//ƒ values() { [native code] } 複製代碼
  • 調用Symbol.iterator屬性,返回的是一個遍歷器對象,該對象具備next方法,next方法返回一個對象具備value和done屬性
  • 由上可得,原生具備iterator接口的數據有:Array,String,Nodelist,arguments,Map,Set

2. 實際應用

  • 對於原生部署了Symbol.iterator屬性的數據結構,不須要本身寫遍歷器生成函數,for...of循環會自動遍歷他們
  • 其餘沒有iterator接口的數據結構則須要在Symbol.iterator屬性上部署,這樣纔會被for...of循環遍歷到
  • 循環遍歷的兩種方式
var arr=[4,6,8]
 // 1. for...of  for(var item of arr){  console.log(item);  }   // 2. 調用Symbol.iterator接口方法  var res=arr[Symbol.iterator]()  console.log(res.next())//{value: 4, done: false}  console.log(res.next())//{value: 6, done: false}  console.log(res.next());//{value: 8, done: false}  console.log(res.next());//{value: undefined, done: true} 複製代碼
  • 對象沒有部署iterator接口的緣由:對象的哪一個屬性先遍歷,哪一個屬性後遍歷是不肯定的。
  • 本質上,遍歷器就是一種線性處理,對於任何非線性的數據結構,部署遍歷器接口,就等於部署一種線性轉換。
class RangeIterator {
 constructor(start, stop) {  this.value = start;  this.stop = stop;  }   [Symbol.iterator]() {  console.log("調用Symbol.iterator屬性")  return this;  }   next() {  console.log("調用next屬性")  var value = this.value;  if (value < this.stop) {  this.value++;  return {done: false, value: value};  }  return {done: true, value: undefined};  }  }   function range(start, stop) {  return new RangeIterator(start, stop);  }  // 獲得一個對象  console.log(range(0, 3));//RangeIterator {value: 0, stop: 3}  // 而後for...of遍歷該對象就至關於一直調用該對象的next方法,直到done爲true  console.log(range(0, 3).__proto__);  /* 該實例的原型具備如下屬性  constructor: class RangeIterator  next: ƒ next()  Symbol(Symbol.iterator): ƒ [Symbol.iterator]()  __proto__: Object  */  for (var value of range(0, 3)) {  console.log(value); // 0, 1, 2  } 複製代碼

3. 給對象部署itrator接口

  • 咱們實際上給對象部署ierator接口能夠直接調用數組的iterator屬性
  • 不推薦像第一節那樣實現iterator接口,並且必須加上length屬性才能在for...of循環獲取到值
  • 類數組對象指的就是具備length屬性的對象,因此length屬性必須有,決定着循環的次數
  • 部署方式:給對象的Symbol.iterator屬性設置爲數組的Symbol.iterator屬性。[Symbol.iterator]:Array.prototype[Symbol.iterator]
  • 例子:
// 1. 對象具備0,1,2這些數字屬性
 var obj1={  0:'a',  1:3333,  2:'w',  length:3,  [Symbol.iterator]:Array.prototype[Symbol.iterator]  }  for(var item of obj1){  console.log(item);//a,3333,w   }   // 2. 沒有數字屬性  var obj2={  length:3,  [Symbol.iterator]:Array.prototype[Symbol.iterator]  }  for(var item of obj2){  console.log(item);//undefined,undefined,undefined  }   // 3. 沒有length屬性(此時啥都沒打印。)  var obj3={  [Symbol.iterator]:Array.prototype[Symbol.iterator]  }  for(var item of obj3){  console.log(item);  }   // 4. 具備非數字屬性,且無length屬性(不打印。)  var obj4={  a:'a',  [Symbol.iterator]:Array.prototype[Symbol.iterator]  }  for(var item of obj4){  console.log(item);  }   // 5. 具備非數字和length屬性  // 會執行length所記錄的長度次數,可是沒有對應索引屬性,只會獲得unefined  var obj5={  a:'a',  length:1,  [Symbol.iterator]:Array.prototype[Symbol.iterator]  }  for(var item of obj5){  console.log(item);// undefined  } 複製代碼

三. 應用場合

1.解構賦值

  • 默認調用Symbol.iterator接口
var set=new Set().add('a').add('b').add('c')
 let [x,y]=set;  console.log(x,y);//a,b  let [a,...b]=set;  console.log(a,b);//a,['b','c'] 複製代碼

2. 拓展運算符

  • 拓展運算符也會調用默認的Symbol.iterator接口
var str="hello"
 console.log([...str]);//["h", "e", "l", "l", "o"]  var arr=['a',2,6]  console.log([...arr]);//["a", 2, 6] 複製代碼

3. yield*

  • yield* 後面是一個可遍歷的結構,那麼就會調用該結構的遍歷器接口
var gen=function *(){
 yield 1;  yield* [2,3,4];  yield 9  }  var res=gen();  console.log(res.next())//{value: 1, done: false}  console.log(res.next())//{value: 2, done: false}  console.log(res.next())//{value: 3, done: false}  console.log(res.next())//{value: 4, done: false}  console.log(res.next());//{value: 9, done: false}  console.log(res.next());//{value: undefined, done: true} 複製代碼

四. 字符串的Iterator接口

  • 字符串也是一個類數組的對象,也有length屬性,也原生具備iterator接口
var str="hi"
 //Symbol.iterator屬性是一個函數返回一個遍歷器對象  console.log(typeof str[Symbol.iterator]);//function   var res=str[Symbol.iterator]();  console.log(res.next());//{value: "h", done: false}  console.log(res.next());//{value: "i", done: false}  console.log(res.next());//{value: undefined, done: true} 複製代碼
  • 能夠改寫Symbol.iterator接口
// var str="hello"
 var str=new String("hello")  console.log([...str]);//["h", "e", "l", "l", "o"]  str[Symbol.iterator]=function *(){  yield 1;  yield 2;  yield 3;  }  console.log([...str]);//[1, 2, 3] 複製代碼

五.Iterator接口和generator函數結合

  • Iterator最簡實現:和generator函數結合
// 形式1
 var res1={  [Symbol.iterator]:function *(){  yield 1;  yield 11;  yield 111;  }  }  console.log([...res1]);//[1, 11, 111]   // 形式2  var res2={  *[Symbol.iterator](){  yield 2;  yield 22;  yield 222;  }  }  console.log([...res2]);//[2, 22, 222] 複製代碼

六. retrun()和throw()

  • 遍歷器對象除了具備next方法,還能夠具備return方法和throw方法
  • 若是是自定義Symbol.iterator方法,那麼return()和throw()可選
  • return方法在for...of循環提早退出的話就會調用return方法
  • 而且return方法必須會返回一個對象!而throw方法主要配合generator函數使用
// return 方法
 var arr=[2,4,6]  arr[Symbol.iterator]=function(){  return {  next(){  return {value:'a',done:false}  },  return(){  return {done:true}  }  }  }  var i=0;  for(var item of arr){  i++;  console.log(item);//a,a,a,a  if(i>3){  // 調用return方法的方式一:break  // break;  // 2.拋出錯誤  throw new Error("err");//Error: err  }  } 複製代碼

七. for...of循環

  • 一個數據結構只要具備Symbol.iterator屬性,就能夠視爲具備Iterator接口,就能夠用for...of循環遍歷它的成員
  • for...of循環內部調用的就是變量自己的Symbol.iterator方法
  • for...in循環用於讀取鍵名,for...of新歡用於讀取鍵值。
  • 而且for...of調用的是遍歷器接口(只會返回具備數字索引的屬性),而for..in不是調用遍歷器接口的,因此能夠獲得非數字索引的屬性
var arr=[1,2,5]
 arr.foo="ffff"   // for...of 循環不能獲得非數字索引的屬性  for(var item of arr){  console.log(item)  // 1,2,5  }   // for ...in   for(var i in arr){  console.log(arr[i])  // 能夠獲得給數字索引的屬性  //1,2,5,ffff  } 複製代碼

1. 數組

  • 數組原生就具備iterator接口
var arr=['a',2,7]
 for(var item of arr){  console.log(item)//a,2,7  }  var obj={}  // 把數組的Symbol.iterator接口設置到對象中  // 注意要使用bind綁定,而不是調用該方法  obj[Symbol.iterator]=arr[Symbol.iterator].bind(arr);  for(var item of obj){  console.log(item);//a,2,7  }   // 給對象添加新的屬性  obj['a']='yy'  obj[3]=3  // 能夠注意到屬性沒有添加到Symbol.iterator中  console.log(obj);//{3: 3, a: "yy", Symbol(Symbol.iterator): ƒ}  // 能夠看到BoundThis中沒有屬性a,3  /*  arguments: [Exception: TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at Function.invokeGetter (<anonymous>:1:142)]  caller: [Exception: TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at Function.invokeGetter (<anonymous>:1:142)]  length: 0  name: "bound values"  __proto__: ƒ ()  [[TargetFunction]]: ƒ values()  [[BoundThis]]: Array(3)  0: "a"  1: 2  2: 7  length: 3  __proto__: Array(0)  [[BoundArgs]]: Array(0)  */  for(var item of obj){  // 沒有遍歷到後面添加到的幾個屬性  console.log(item);//a,2,7  } 複製代碼

2.Set和Map結構

  • Set和Map結構原生具備Iterator接口,能夠直接使用for...of循環
// 1. set
 var set=new Set()  set.add(1)  set.add(10)  set.add('a')  for(var item of set){  console.log(item);//1,10,a   }   // 2. Map   var map=new Map();  map.set('a',1)  map.set('ab',11)  map.set('ba',1081)  for(var item of map){  console.log(item)  // ["a", 1],["ab", 11],["ba", 1081]  } 複製代碼

3.類數組的對象

  • 相似數組的對象包括nodelist,arguments,字符串...
// 1.字符串
 var str="hello"  for(var item of str){  console.log(item);//h,e,l,l,o   }  // 2. nodelist對象  var nodes=document.getElementsByClassName('one')  for(var item of nodes){  console.log(item);//<div class="one"></div>  }  // 3. arguments對象  function args(){  for(var item of arguments){  console.log(item);//5,'w',98  }  }  args(5,'w',98) 複製代碼
  • 可是對於對象來講,即便添加了length屬性成爲類數組對象
  • 還須要改寫Symbol.iterator方法才能夠循環遍歷屬性。或者使用Array.from方法轉換爲數組先
var obj={length:2,0:1,1:'e',t:333}
 // 雖然是類數組對象,可是沒有原生的Symbol.iterator方法,也沒有重寫  // 因此仍是會報錯  /* for(var item of obj){  console.log(item);//TypeError obj is not iterable  } */   // 解決方法1,設置Symbol.iterator方法 /* obj[Symbol.iterator]=Array.prototype[Symbol.iterator]  for(var item of obj){  console.log(item);//1,e  } */   // 解決方法2:使用Array.from轉換爲數組  obj=Array.from(obj)  for(var item of obj){  console.log(item);//1,e  } 複製代碼
  • 若是不想添加屬性Symbol.iterator或者轉換爲數組,那麼
  • 方法:1.經過for..in循環間接獲取鍵值,2.經過Object.keys()生成數組再遍歷
var obj={a:2,0:'ss',1:111}
 // 1. for...in循環  for(var i in obj){  console.log(obj[i]);//ss,111,2  }  // 2. Object.keys()生成數組  for(var item of Object.keys(obj)){  console.log(obj[item])//ss,111,2  } 複製代碼

4.forEach循環

  • forEach循環的優勢在於寫起來簡潔,缺點在於不可使用break,continue,return實現退出!
var arr=[4,5,77,0,323]
 arr.forEach((item,i)=>{  console.log(item);//4,5,77,0,323  if(i==2){  // return ;// 退出失敗。不報錯  // break;//報錯,Illegal break statement  // continue;// 報錯:Illegal continue statement: no surrounding iteration statement  }  }) 複製代碼

本文參考 阮一峯ES6教程 node

本文使用 mdnice 排版es6

相關文章
相關標籤/搜索