ES6 详解之迭代器 for of
起源
一段标准的 for 循环代码:
var colors = ['red','green','white'];
for(var i = 0,len = colors.length;i<len;i++){
console.log(colors[i]);
}
看着很简单,但是再回顾这段代码,实际上我们仅仅是需要数组中元素的值,但是却需要提前获取数组长度,声明索引变量等,尤其当多个循环嵌套的时候,更需要使用多个索引变量,代码的复杂度就会大大增加,比如我们使用双重循环进行去重:
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;
}
迭代器
所谓迭代器,其实就是一个具有 next() 方法的对象,每次调用 next() 都会返回一个结果对象,该结果对象有两个属性,value 表示当前的值,done 表示遍历是否结束。
用 ES5 的语法创建一个迭代器:
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
};
}
};
}
// iterator 就是一个迭代器对象
var iterator = createIterator([1,2,3,4]);
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: false, value: 4}
console.log(iterator.next()); //{done: true, value: undefined}
for of
除了迭代器之外,我们还需要一个可以遍历迭代器对象的方式,ES6 提供了 for of 语句,我们直接用 for of 遍历一下我们上节生成的遍历器对象试试:
var arr = createIterator([1,2,3,4]);
for (const value of arr) {
console.log(value);
}
报错了: Uncaught TypeError: arr[Symbol.iterator] is not a function,表明我们生成的 iterator 对象并不是 iterable 可遍历的。
其实一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。
ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是"可遍历的"(iterable)。举个例子:
var obj ={
value:1
}
for (const value of obj) {
console.log(value);
}
报错 :Uncaught TypeError: obj[Symbol.iterator] is not a function
我们直接 for of 遍历一个对象,会报错,然而如果我们给该对象添加 Symbol.iterator 属性:
var obj ={};
obj[Symbol.iterator] = function(){
return createIterator([1,2,3,4]);
}
for (const value of obj) {
console.log(value);
}
由此,我们也可以发现 for of 遍历的其实是对象的 Symbol.iterator 属性。
默认可遍历对象
数组默认可遍历
模拟实现 for of
其实模拟实现 for of 也比较简单,基本就是通过 Symbol.iterator 属性获取迭代器对象,然后使用 while 遍历一下:
// 模拟实现 for of
function forOf(obj,cb){
let iterable,result;
console.log(typeof obj[Symbol.iterator]);
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 集合内建了以下三种迭代器:
- entries()返回一个遍历器对象,用来遍历[key,value]组成的数组,对于数组键名就是索引值
- keys() 返回一个遍历器对象,用来遍历所有的键名
- values()返回一个遍历器对象,用来遍历所有的键值
以数组为例:
let colors = ["red","black","orange","blue"];
for (const index of colors.keys()) {
console.log(index); // 0,1,2,3
}
for (const value of colors.values()) {
console.log(value); // red,black,orange,blue
}
for (const item of colors.entries()) {
console.log(item); // [0,"red"] ,[1,"black"],[2,"orange"],[3,"blue"]
}
for (const [k,v] of colors.entries()) {
console.log(k+":"+v); // 0 :red ,1:black,2:orange,3:blue
}
Map 类型与数组类似,但是对于 Set 类型需要注意以下:
var colors = new Set(["red","black","orange","blue"]);
for (const index of colors.keys()) {
console.log(index); // red black orange blue
}
for (const color of colors.values()) {
console.log(color); // red black orange blue
}
for (const color of colors.entries()) {
console.log(color); // ["red","red"],["black","black"],["orange","orange"],["blue","blue"]
}
Set 类型的 keys() 和 values() 返回的是相同的迭代器,这也意味着在 Set 这种数据结构中键名与键值相同。
而且每个集合类型都有一个默认的迭代器,在 for-of 循环中,如果没有显式指定则使用默认的迭代器。
数组和 Set 集合的默认迭代器是 values() 方法,Map 集合的默认迭代器是 entries() 方法。
遍历 Map 数据结构的时候可以顺便结合解构赋值
let colors = ["red","black","orange","blue"];
for (const [k,v] of colors.entries()) {
console.log(k+":"+v); // 0 :red ,1:black,2:orange,3:blue
}
Babel 是如何编译 for of 的
const colors = new Set(["red","green","blue"]);
for (const 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])
}
通过 Bable 编译的 for 循环表达式通过 while 来重写
var _itera = colors[Symbol.iterator](),_step;
while(!(_iteratorNormalCompletion = (_step = _iterator.next()).done)){
var color = _step.calue;
console.log(color);
_iteratorNormalCompletion = true;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论