深入了解Symbol,比如Symbol.toStringTag,Symbol.toPrimitive等其他Symbol属性可以看看我之前的文章:
JavaScript中的Symbol类型 
Iterator(遍历器) 迭代器模式 
迭代器模式是指提供一种方法顺序访问 一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素 。
 
Iterator的概念 
遍历器(Iterator)就是一种统一的接口机制,用来处理遍历所有不同的数据结构。  任何数据结构只要部署 Iterator 接口 ,就可以完成遍历 操作(即依次处理该数据结构的所有成员)。
 
Iterable 是指有 [Symbol.iterator] 属性的对象 ,这个属性 obj[Symbol.iterator] 就是 Iterator(迭代器)。也可以说可迭代对象是实现了  Symbol.iterator 方法的对象 。
凡是部署了 Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。 
如果  Symbol.iterator方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。  遍历器对象的根本特征就是具有  next方法。 
 
原生具备Iterator接口的数据结构 
Array 
Set 
Map 
String 
TypeArray: 类似于数组的结构,对底层的二进制进行描述的一种数据结构 
函数的argiuments对象 
NodeList对象 
 
以上数据类型,都有 Symbol.iterator 属性,属性值是一个函数,执行这个函数就会返回一个迭代器。这个迭代器就有 next 方法可顺序迭代子元素 
判断某个对象是否可迭代:  
const isIterable = obj => obj != null && typeof obj[Symbol.iterator] === 'function';
Iterator 的作用 
迭代器Iterator是一种接口机制,为各种不同的数据结构提供统一的访问机制 
主要就是供 for...of.. 使用 使数据结构的成员按照某种顺序排列 
用一句话总结就是:让不可迭代的数据结构变得可迭代  
 
Iterator 的遍历过程 
创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。 
第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员。 
第二次调用指针对象的 next 方法,指针就指向数据结构的第二个成员。 
不断调用指针对象的 next 方法,直到它指向数据结构的结束位置。 
 
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含  value和  done两个属性的对象。其中,  value属性是当前成员的值,  done属性是一个布尔值,表示遍历是否结束。 
模拟next()实现一个遍历器 
注意:Iterator 只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function  idMaker (  let  index = 0 ;   return  {     next : function  (       return  {value : index++, done : false }     }   } } let  it = new  idMaker (); console .log (it.next ()); console .log (it.next ()); console .log (it.next ()); console .log (it.next ()); 
使用ES6的class语法模拟 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class  MakeIterator  {  constructor (list ) {     this .list  = list;     this .index  = 0 ;   }   next (     if  (this .index  < this .list .length ) {       return  {         value : this .list [this .index ++],         done : false        }     } else  {       return  {         value : undefined ,         done : true        }     }   } } let  iterator = new  MakeIterator ([1 ,2 ,3 ]);console .log (iterator.next ()); console .log (iterator.next ()); console .log (iterator.next ()); console .log (iterator.next ()); 
使用function来模拟 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function  makeIterator (list ) {  let  index = 0 ; 	      return  {     next : function  (              return  index < list.length  ?        	{value : list[index++], done : false } : {value : undefined , done : true }     }   } } const  iterator = makeIterator ([1 ,3 ,5 ]);console .log (iterator.next ()); console .log (iterator.next ()); console .log (iterator.next ()); console .log (iterator.next ()); 
使用Generator函数来模拟 Symbol.iterator()方法的最简单实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 let  myIterable = {  [Symbol .iterator ]: function * () {     yield  1 ;     yield  2 ;     yield  3 ;   } }; [...myIterable]  let  obj = {  * [Symbol .iterator ]() {     yield  'hello' ;     yield  'world' ;   } }; for  (let  x of  obj) {  console .log (x); } 
遍历器对象的 return(),throw() 
遍历器对象除了具有next()方法,还可以具有return()方法和throw()方法。
如果自己写遍历器对象生成函数,那么 next() 方法是必须部署的 ,return()方法和throw()方法是否部署是可选的。
 
return() 方法的使用场合是,如果 for...of循环提前退出(通常是因为出错,或者有 break语句),就会调用 return() 方法。  注意  ,return()方法必须返回一个对象  ,这是 Generator 语法决定的。  如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return()方法。throw()方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function  readLinesSync (file ) {  return  {     [Symbol .iterator ]() {       return  {         next (           return  { done : false  };         },         return () {           file.close ();           return  { done : true  };         }       };     },   }; } for  (let  line of  readLinesSync (fileName)) {  console .log (line);   break ; } for  (let  line of  readLinesSync (fileName)) {  console .log (line);   throw  new  Error (); } 
可迭代协议和迭代器协议 
通俗来理解,具有 Symbol.iterator的就是遵循的可迭代协议 。凡是按照 next() 调用这种就都可以称之为迭代器协议。 
 
迭代器协议(iterator protocol)  : iterator协议定义了产生value序列的一种标准方法。只要实现符合要求的next函数,该对象就是一个迭代器。相当遍历数据结构元素的指针,类似数据库中的游标。
在 JavaScript 中迭代器是一个对象 ,它提供了一个  next() 方法 ,用来返回序列中的下一项 。这个方法返回包含两个属性:  done 和  value。 
 
可迭代协议(iterable protocol)  : 一旦支持可迭代协议,意味着该对象可以用for-of来遍历,可以用来定义或者定制 JS 对象的迭代行为。常见的内建类型比如Array & Map都是支持可迭代协议的。对象必须实现@@iterator方法,意味着对象必须有一个带有@@iterator key的可以通过常量Symbol.iterator访问到的属性。
 
将一段不可遍历的数据结构变得可遍历:
可以用Generator函数进行改造:
实现一个迭代器 基于迭代器协议 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function  createArrayIterator (arr ) {  let  index = 0    return  {     next : () =>      index < arr.length      ? { value : arr[index++], done : false  }     : { value : undefined , done : true  },   } } const  nums = [11 , 22 , 33 , 44 ]const  numsIterator = createArrayIterator (nums)console .log (numsIterator.next ())console .log (numsIterator.next ())console .log (numsIterator.next ())console .log (numsIterator.next ())
基于可迭代协议 实现了生成迭代器方法的对象称为 可迭代对象 也就是说这个对象中包含一个方法, 该方法返回一个迭代器对象 
一般使用  Symbol.iterator来定义该属性, 学名叫做  @@iterator 方法 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const  obj = {  names : ['111' , '222' , '333' ],   [Symbol .iterator ]() {     let  index = 0      return  {       next : () =>        index < this .names .length        ? { value : this .names [index++], done : false  }       : { value : undefined , done : true  },       return : () =>  {         console .log ('迭代器提前终止了...' )         return  { value : undefined , done : true  }       },     }   }, } for  (const  item of  obj) {  console .log (item)   if  (item === '222' ) break  } 
基于生成器Generator 1 2 3 4 5 6 7 8 9 10 11 function * creatIterator  (){    yield  1      yield  2      yield  3  } const  iterator = creatIterator ()console .log (iterator.next ()) console .log (iterator.next ()) console .log (iterator.next ()) console .log (iterator.next ()) 
调用 Iterator 接口的场合 解构赋值 1 2 3 4 5 6 7 let  set = new  Set ().add ('a' ).add ('b' ).add ('c' );let  [x,y] = set;let  [first, ...rest] = set;
扩展运算符 1 2 3 4 5 6 7 8 const  str = 'hello' ;[...str]  let  arr = ['b' , 'c' ];['a' , ...arr, 'd' ] 
实际上,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,转为数组。也就是说,只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组 。即let arr=[...iterable];
yield*1 2 3 4 5 6 7 8 9 10 11 12 13 14 let  generator = function * () {  yield  1 ;   yield * [2 ,3 ,4 ];   yield  5 ; }; const  iterator = generator ();iterator.next ()  iterator.next ()  iterator.next ()  iterator.next ()  iterator.next ()  iterator.next ()  
其他场合 
for...ofArray.from()Map(),Set(),WeakMap(),WeakSet()(比如new Map([['a',1],['b',2]]))Promise.all()Promise.race() 
for...of...循环1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function  each (data ){  const  iterator = data[Symbol .iterator ]()   let  item = iterator.next ()   while (!item.done ){     console .log (item.value )     item = iterator.next ()   } } each ([1 ,2 ,3 ,4 ,5 ])
ES6  借鉴 C++、Java、C# 和 Python 语言,引入了 for…of 循环,作为遍历所有数据结构的统一的方法 。一个数据结构只要部署了  Symbol.iterator 属性 ,就被视为具有 iterator 接口,就可以用  for...of 循环遍历它的成员 。
Array 
Set 
Map 
String 
TypeArray:类似于数组的结构,对底层的二进制进行描述的一种数据结构 
函数的argiuments对象 
NodeList对象 
Generator 对象 
 
以上这些可以直接使用for...of 循环。
除此之外,其他数据结构(主要是对象)的  Iterator 接口,都需要自己在 Symbol.iterator属性上面部署,这样才会被 for...of循环遍历 。
数组 数组原生具备  iterator 接口(即默认部署了  Symbol.iterator 属性)  ,for…of 循环本质上就是调用这个接口产生的遍历器。
1 2 3 4 5 6 7 8 9 10 11 12 let  arr = ['red' ,'green' ,'blue' ];for  (let  item of  arr) {  console .log (item);  } let  obj = {};obj[Symbol .iterator ] = arr[Symbol .iterator ].bind (arr); for  (let  item of  obj) {  console .log (item);  } 
注意:  for...of 循环  调用遍历器接口,数组的遍历器接口  只返回具有数字索引的属性  。这一点跟  for...in 循环不一样。 
1 2 3 4 5 6 7 8 9 10 let  arr = [1 ,2 ,3 ];arr.foo  = 'webchang' ; for  (let  i in  arr) {  console .log (i);  } for  (let  i of  arr) {  console .log (i);  } 
Set 和 Map 结构 Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用 for...of 循环。
遍历的顺序 是按照各个成员被添加进数据结构的顺序 。Set 结构遍历时,返回的是一个值 ,而 Map 结构遍历时,返回的是一个数组 ,该数组的两个成员分别为当前 Map 成员的键名和键值。 
1 2 3 4 5 let  names = new  Set (['小明' ,'webchang' ,'小红' ,'webchang' ]);for  (let  item of  names) {  console .log (item);  } 
1 2 3 4 5 6 7 8 9 10 11 12 let  students = new  Map ();students.set ('001' ,'小明' ); students.set ('002' ,'webchang' ); students.set ('003' ,'小红' ); for  (let  item of  students) {  console .log (item); } for  (let  [num,name] of  students) {  console .log (num,name); } 
计算生成的数据结构 
entries() *返回一个遍历器对象, 用来遍历[键名, 键值] 组成的数组 。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用 entries 方法。keys() 返回一个遍历器对象,用来遍历所有的键名 。values() 返回一个遍历器对象,用来遍历所有的键值 。 
1 2 3 4 5 6 7 let  arr = ['a' , 'b' , 'c' ];for  (let  pair of  arr.entries ()) {  console .log (pair); } 
类似数组的对象 for...of 循环用于字符串、DOM NodeList 对象、arguments 对象的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 let  str = "hello" ;for  (let  s of  str) {  console .log (s);  } let  paras = document .querySelectorAll ("p" );for  (let  p of  paras) {  p.classList .add ("test" ); } function  printArgs (  for  (let  x of  arguments ) {     console .log (x);   } } printArgs ('a' , 'b' );
注意:并不是所有类似数组的对象(存在数值键名和length属性)都具有 Iterator 接口: 
1 2 let  arrayLike = { length : 2 , 0 : 'a' , 1 : 'b'  };
可以对这些类似数组的对象部署 Iterator 接口,有一个简便方法,就是 Symbol.iterator 方法直接引用数组的 Iterator 接口,例如:(NodeList 对象是类似数组的对象,本来就具有遍历接口,可以直接遍历 。)
1 2 3 NodeList .prototype Symbol .iterator ] = Array .prototype Symbol .iterator ];NodeList .prototype Symbol .iterator ] = [][Symbol .iterator ];
注意:类数组对象才会有 Symbol.iterator 方法,普通对象部署数组的 Symbol.iterator 方法,并无效果。 
1 2 3 4 5 6 7 8 9 10 let  iterable = {  0 : 'a' ,   1 : 'b' ,   2 : 'c' ,   length : 3 ,   [Symbol .iterator ]: Array .prototype Symbol .iterator ] }; for  (let  item of  iterable) {  console .log (item);  } 
1 2 3 4 5 6 7 8 9 10 let  iterable = {  a : 'a' ,   b : 'b' ,   c : 'c' ,   length : 3 ,   [Symbol .iterator ]: Array .prototype Symbol .iterator ] }; for  (let  item of  iterable) {  console .log (item);  } 
对于普通的对象,for…of 结构不能直接使用 对于普通的对象 ,for...of 结构不能直接使用 ,会报错,必须部署了 Iterator 接口后才能使用。
一个对象如果要具备可被 for...of循环调用的 Iterator 接口,就必须在 Symbol.iterator 的属性上部署遍历器生成方法(  原型链上的对象具有该方法也可  )  。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class  RangeIterator  {  constructor (start, end ) {     this .value  = start;     this .stop  = end;   }   [Symbol .iterator ]() {     return  this ;   }   next (     let  value = this .value ;     if  (value < this .stop ) {       this .value ++;       return  {value : this .value , done : false };     }     return  {value : undefined , done : true }   } } let  range = new  RangeIterator (0 ,3 );for  (let  item of  range) {  console .log (item); } console .log (range);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let  obj = {  data : ['web' , 'chang' ],   [Symbol .iterator ]: function  (     let  self = this ;     let  index = 0 ;     return  {       next : function  (         if  (index < self.data .length ) {           return  {value : self.data [index++], done : false }         }         return  {value : undefined , done : true }       }     }   } } for  (let  item of  obj) {  console .log (item);  } 
1 2 3 4 5 6 7 8 9 10 11 12 function * entries (obj ) {  for  (let  key of  Object .keys (obj)) {     yield  [key, obj[key]];   } } for  (let  [key, value] of  entries (obj)) {  console .log (key, '->' , value); } 
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 let  price = {  a : [1 , 2 , 3 ],   b : [6 , 5 , 4 ],   c : [8 , 7 , 9 ],   [Symbol .iterator ]() {     let  arr = []     for  (let  [key, value] of  Object .entries (this )) {       arr.push (Math .min (...value));     }     let  index = 0 ;     return  {       next (         if  (index < arr.length ) {           return  {value : arr[index++], done : false }         }         return  {value : undefined , done : true }       }     }   } } for  (let  item of  price) {  console .log (item);  } 
与其他遍历语法的比较 for循环 1 2 3 for  (let  index = 0 ; index < myArray.length ; index++) {  console .log (myArray[index]); } 
数组的forEach方法 这种写法的问题在于,无法中途跳出forEach循环,break命令或return命令都不能奏效。 
1 2 3 myArray.forEach (function  (value ) {   console .log (value); }); 
for...in...循环for...in 循环可以遍历数组的键名。for...in 循环主要是为遍历对象而设计的,不适用于遍历数组 。
1 2 3 for  (let  index in  myArray) {  console .log (myArray[index]); } 
for...in 循环有几个缺点:
数组的键名是数字 ,但是for...in 循环是以字符串作为键名的 ,“0”、“1”、“2”等等。for...in 循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键 某些情况下,  for...in 循环会以  任意顺序  遍历键名 。