一段标准的 for 循环代码:git
var colors = ["red", "green", "blue"]; for (var i = 0, len = colors.length; i < len; i++) { console.log(colors[i]); }
看着很简单,可是再回顾这段代码,实际上咱们仅仅是须要数组中元素的值,可是却须要提早获取数组长度,声明索引变量等,尤为当多个循环嵌套的时候,更须要使用多个索引变量,代码的复杂度就会大大增长,好比咱们使用双重循环进行去重:es6
function unique(array) { var res = []; for (var i = 0, arrayLen = array.length; i < arrayLen; i++) { for (var j = 0, resLen = res.length; j < resLen; j++) { if (array[i] === res[j]) { break; } } if (j === resLen) { res.push(array[i]); } } return res; }
为了消除这种复杂度以及减小循环中的错误(好比错误使用其余循环中的变量),ES6 提供了迭代器和 for of 循环共同解决这个问题。github
所谓迭代器,其实就是一个具备 next() 方法的对象,每次调用 next() 都会返回一个结果对象,该结果对象有两个属性,value 表示当前的值,done 表示遍历是否结束。数组
咱们直接用 ES5 的语法建立一个迭代器:浏览器
function createIterator(items) { var i = 0; return { next: function() { var done = i >= item.length; var value = !done ? items[i++] : undefined; return { done: done, value: value }; } }; } // iterator 就是一个迭代器对象 var iterator = createIterator([1, 2, 3]); console.log(iterator.next()); // { done: false, value: 1 } console.log(iterator.next()); // { done: false, value: 2 } console.log(iterator.next()); // { done: false, value: 3 } console.log(iterator.next()); // { done: true, value: undefined }
除了迭代器以外,咱们还须要一个能够遍历迭代器对象的方式,ES6 提供了 for of 语句,咱们直接用 for of 遍历一下咱们上节生成的遍历器对象试试:babel
var iterator = createIterator([1, 2, 3]); for (let value of iterator) { console.log(value); }
结果报错 TypeError: iterator is not iterable
,代表咱们生成的 iterator 对象并非 iterable(可遍历的)。数据结构
那什么才是可遍历的呢?异步
其实一种数据结构只要部署了 Iterator 接口,咱们就称这种数据结构是“可遍历的”(iterable)。函数
ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具备 Symbol.iterator 属性,就能够认为是"可遍历的"(iterable)。code
举个例子:
const obj = { value: 1 }; for (value of obj) { console.log(value); } // TypeError: iterator is not iterable
咱们直接 for of 遍历一个对象,会报错,然而若是咱们给该对象添加 Symbol.iterator 属性:
const obj = { value: 1 }; obj[Symbol.iterator] = function() { return createIterator([1, 2, 3]); }; for (value of obj) { console.log(value); } // 1 // 2 // 3
由此,咱们也能够发现 for of 遍历的实际上是对象的 Symbol.iterator 属性。
然而若是咱们直接遍历一个数组对象:
const colors = ["red", "green", "blue"]; for (let color of colors) { console.log(color); } // red // green // blue
尽管咱们没有手动添加 Symbol.iterator 属性,仍是能够遍历成功,这是由于 ES6 默认部署了 Symbol.iterator 属性,固然咱们也能够手动修改这个属性:
var colors = ["red", "green", "blue"]; colors[Symbol.iterator] = function() { return createIterator([1, 2, 3]); }; for (let color of colors) { console.log(color); } // 1 // 2 // 3
除了数组以外,还有一些数据结构默认部署了 Symbol.iterator 属性。
因此 for...of 循环可使用的范围包括:
其实模拟实现 for of 也比较简单,基本就是经过 Symbol.iterator 属性获取迭代器对象,而后使用 while 遍历一下:
function forOf(obj, cb) { let iterable, result; if (typeof obj[Symbol.iterator] !== "function") throw new TypeError(result + " is not iterable"); if (typeof cb !== "function") throw new TypeError("cb must be callable"); iterable = obj[Symbol.iterator](); result = iterable.next(); while (!result.done) { cb(result.value); result = iterable.next(); } }
为了更好的访问对象中的内容,好比有的时候咱们仅须要数组中的值,但有的时候不只须要使用值还须要使用索引,ES6 为数组、Map、Set 集合内建了如下三种迭代器:
以数组为例:
var colors = ["red", "green", "blue"]; for (let index of colors.keys()) { console.log(index); } // 0 // 1 // 2 for (let color of colors.values()) { console.log(color); } // red // green // blue for (let item of colors.entries()) { console.log(item); } // [ 0, "red" ] // [ 1, "green" ] // [ 2, "blue" ]
Map 类型与数组相似,可是对于 Set 类型须要注意如下:
var colors = new Set(["red", "green", "blue"]); for (let index of colors.keys()) { console.log(index); } // red // green // blue for (let color of colors.values()) { console.log(color); } // red // green // blue for (let item of colors.entries()) { console.log(item); } // [ "red", "red" ] // [ "green", "green" ] // [ "blue", "blue" ]
Set 类型的 keys() 和 values() 返回的是相同的迭代器,这也意味着在 Set 这种数据结构中键名与键值相同。
并且每一个集合类型都有一个默认的迭代器,在 for-of 循环中,若是没有显式指定则使用默认的迭代器。数组和 Set 集合的默认迭代器是 values() 方法,Map 集合的默认迭代器是 entries() 方法。
这也就是为何直接 for of 遍历 Set 和 Map 数据结构,会有不一样的数据结构返回:
const values = new Set([1, 2, 3]); for (let value of values) { console.log(value); } // 1 // 2 // 3
const values = new Map([["key1", "value1"], ["key2", "value2"]]); for (let value of values) { console.log(value); } // ["key1", "value1"] // ["key2", "value2"]
遍历 Map 数据结构的时候能够顺便结合解构赋值:
const valuess = new Map([["key1", "value1"], ["key2", "value2"]]); for (let [key, value] of valuess) { console.log(key + ":" + value); } // key1:value1 // key2:value2
咱们能够在 Babel 的 Try it out 中查看编译的结果:
const colors = new Set(["red", "green", "blue"]); for (let color of colors) { console.log(color); }
对于这样一段代码,编译的结果以下:
"use strict"; var colors = new Set(["red", "green", "blue"]); var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for ( var _iterator = colors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true ) { var color = _step.value; console.log(color); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } }
至少由编译的结果能够看出,使用 for of
循环的背后,仍是会使用 Symbol.iterator 接口。
而这段编译的代码稍微复杂的地方有两段,一段是 for 循环这里:
for ( var _iterator = colors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true ) { var color = _step.value; console.log(color); }
跟标准的 for 循环写法有些差异,咱们看下 for 语句的语法:
for (initialize; test; increment) statement;
initialize、test 和 increment 三个表达式之间用分号分割,它们分别负责初始化操做
、循环条件判断
和计数器变量的更新
。
for 语句其实就至关于:
initialize; while (test) { statement; increment; }
代码的逻辑为:先进行初始化,而后每次循环执行以前会执行 test 表达式,并判断表达式的结果来决定是否执行循环体,若是 test 计算结果为真值,则执行循环体中的 statement。最后,执行 increment 表达式。
并且值得注意的是,其实 for 循环中的三个表达式中任意一个均可以被忽略,不过度号仍是要写的。
好比 for(;;)
,不过这就是一个死循环……
好比:
var i = 0, len = colors.length; for (; i < len; i++) { console.log(colors[i]); }
又好比:
var i = 0, len = colors.length; for (; i < len; ) { i++; }
而后咱们再来看 Babel 编译的这个 for 循环表达式:
for ( var _iterator = colors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true ) { var color = _step.value; console.log(color); }
用 while 的写法至关于:
var _iterator = colors[Symbol.iterator](), _step; while (!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) { var color = _step.value; console.log(color); _iteratorNormalCompletion = true; }
是否是就好懂了不少呢,而后你就会发现,其实 _iteratorNormalCompletion = true
这句是彻底没有必要的……
另一段稍微复杂的代码是:
try { ... } catch (err) { ... } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { ... } }
由于 _iteratorNormalCompletion = (_step = _iterator.next()).done
,因此 _iteratorNormalCompletion 表示的就是是否完成了一次完整的迭代过程,若是没有正常的迭代完成,而且迭代器有 return 方法时,就会执行该方法。
而之因此这么作,就要提到迭代器的 return 方法。
引用阮一峰老师的 ECMAScript 6 入门:
遍历器对象除了具备 next 方法,还能够具备 return 方法和 throw 方法。若是你本身写遍历器对象生成函数,那么 next 方法是必须部署的,return 方法和 throw 方法是否部署是可选的。return 方法的使用场合是,若是 for...of 循环提早退出(一般是由于出错,或者有 break 语句或 continue 语句),就会调用 return 方法。若是一个对象在完成遍历前,须要清理或释放资源,就能够部署 return 方法。
咱们能够举个例子:
function createIterator(items) { var i = 0; return { next: function() { var done = i >= items.length; var value = !done ? items[i++] : undefined; return { done: done, value: value }; }, return: function() { console.log("执行了 return 方法"); return { value: 23333, done: true }; } }; } var colors = ["red", "green", "blue"]; var iterator = createIterator([1, 2, 3]); colors[Symbol.iterator] = function() { return iterator; }; for (let color of colors) { if (color == 1) break; console.log(color); } // 执行了 return 方法
不过正如你在编译后的代码中看到,仅仅是在有 return 函数的时候执行了 return 函数而已,return 函数中返回的值其实并不生效……
可是你不返回值或者返回一个基本类型的值的话,结果又会报错……
TypeError: Iterator result undefined is not an object
这是由于 return 方法必须返回一个对象,而这又是 Generator 规范决定的……
总之若是是在浏览器中使用的话,return 函数的返回值其实并不生效 T^T
ES6 系列目录地址:https://github.com/mqyqingfeng/Blog
ES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级做用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。
若是有错误或者不严谨的地方,请务必给予指正,十分感谢。若是喜欢或者有所启发,欢迎 star,对做者也是一种鼓励。