JavaScript浅谈之迭代器(Iterator) 和for-of循环

起源

使用for循环遍历数组

我们如何遍历数组元素?在javascript刚出现的时候,我们可能会这样进行数组遍历

for (var i = 0; index < Array.length; index++) {
  console.log(Array[i]);
}
复制代码

代码看起来很简单对吧,但是仔细阅读代码,我们仅仅只需要取数组元素的值,却需要提前获取数组长度和声明索引变量等,当有多层循环嵌套的时候,就需要更多的索引变量,大大增加了代码的复杂度。于是乎,在ES5中就出现了for-Each方法来进行遍历

使用forEach遍历数组

使用forEach遍历数组,可以这样写

Array.forEach(function (value) {
    console.log(value);
})
复制代码

这段代码看起来很简单了,但是也有一些小问题,不能使用break语句中断循环也不能使用return语句返回到外层函数,当然了,使用for循环去遍历数组也是个不错的选择, 不过,还可以用一种方式遍历数组,那就是for-in

for-in遍历数组

使用for-in遍历数组,代码如下

for (var i in Array) { // 千万别这样做
  console.log(Array[i]);
}
复制代码

但是这个选择建议不要使用,为什么呢?

  • 在这段代码中 i的值实际上不是下标的数字,而是字符串"0","1","2","3",此时很可能在无意间进行字符串运算,例如 “2” + 1 == “21” ,这样会给编码过程带来很大的麻烦
  • for-in 不仅可以遍历数组元素,还可以遍历自定义属性,例如,如果你的数组中有一个可枚举属性Array.username,循环将额外进行遍历一次,遍历到索引为‘username'的索引
  • for-in还有可能会随机顺序遍历数组元素 总之,for-in是为普通对象量身定制的,你可以遍历得到字符串类型的键,因此不适用于数组遍历。 现在我们来讲一个高大上的数组遍历方式-for-of循环

for-of循环

简介

自从引入ES6以来,就增加了很多新的东西,而for-of循环就是其中一种,话不多说,直接上代码看看for-of循环长什么样,其实很简单

for (var value of Array) {
  console.log(value);
}
复制代码

看到这行代码是不是觉得很眼熟,对的,长得很像for-in把,我们来探讨一下for-of循环的外表下到底隐藏了多少强大的功能吧,首先需要记住以下几点

  • for-of是最简单,最直接的遍历数组元素的语法
  • 成功的避开了for-in循环的所有缺陷
  • 与forEach不同的是,它可以正确的相应break,continue和return语句

for-of可以用来干什么呢?

首先,他可以用来遍历对象属性,也可以用来遍历数据(数组中的值),不仅如此!for-of循环还可以遍历其他的集合 for-of不仅支持数组遍历,还支持大多数类数组对象,例如DOM NodeList for-of循环也支持字符串遍历, 同样,也支持Map和Set对象遍历。举个简单的例子

 // 基于单词数组创建一个set对象
var Words = new Set(words);
复制代码

生成了一个Set对象之后,就可以轻松地使用for-of遍历它所包含的内容了。

for (var value of Words) {
   console.log(value);
}
复制代码

遍历Map对象与遍历Set对象稍有不同,那是因为Map比较特殊,它里面的数据有键值对组成,所以需要用解构(destructuring)来将键值对拆解为两个独立的变量

for (var [key, value] of BookMap) {
   console.log(key + "'s phone number is: " + value);
}
复制代码

解构,他是ES6新增的特性下面对解构进行一个简单的了解

解构

什么是解构赋值? 解构赋值允许你使用类似数组或对象字面量的语法将数组和对象的属性赋给各种变量。这种赋值语法极度简洁,同时还比传统的属性访问方法更为清晰。

通常来讲,你很可能这样访问数组中的前三个元素:

var first = array[0]
var second = array[1]
var third = array[2]
复制代码

如果使用解构赋值的特性,将会使等效的代码变得更加简洁并且可读性更高:

var [first, second, third] = array
复制代码

以上是数组解构赋值的一个简单示例,其语法的一般形式为:

[ variable1, variable2, ..., variableN ] = array;
复制代码

关于解构我就不一一讲述了,现在我们回到主题,for-of循环的强大之处,未来的JS可以使用一些新型的集合类,甚至会有更多的类型陆续诞生,而for-of就是为遍历所有这些集合特别设计的循环语句。 for-of循环不支持普通对象,但如果你想迭代一个对象的属性,你可以用for-in循环(这也是它的本职工作)或者内建方Object.keys()

// 向控制台输出对象的可枚举属性
for (var key of Object.keys(Object)) {
  console.log(key + ": " + Object[key]);
}
复制代码

内部原理

被添加到ES6的那些新特性并不是无章可循,里面大多数特性其他语言都在使用,也证明了这些特性很有用,就拿 for-of 语句来说,在 C++、JAVA、C# 和 Python 中都存在类似的循环语句,并且用于遍历这门语言和其标准库中的各种数据结构。 与其他语言中的 for 和 foreach 语句一样,for-of 要求被遍历的对象实现特定的方法。所有的 Array、Map 和 Set 对象都有一个共性,那就是他们都实现了一个迭代器(iterator)方法,那么对于其他对象呢?不着急,也可以自己实现一个迭代器方法,这就好比你对一个对象进行toString操作,来告知js如何将一个对象转换成字符串,也可以为任意对象实现一个ObjectSymbol.iterator方法,来告诉js如何去遍历这个对象。 看到这里,大脑里也许会想,[Symbol.iterator] 这个语法到底是什么鬼,是什么意思,ES 标准委员会完全可以将该方法命名为 iterator(),但是,现有对象中可能已经存在名为“iterator”的方法,这将导致代码混乱,违背了最大兼容性原则。所以,标准委员会引入了 Symbol,而不仅仅是一个字符串,来作为方法名。 一个拥有 Symbol.iterator 方法的对象被认为是可遍历的(iterable)。

迭代器对象

一般情况下,我们不需要从0开始实现一个迭代器对象,现在我们来看看,迭代器具体是什么样的, 就拿for-of语句来说吧,,它首先调用被遍历集合对象的 Symbol.iterator 方法,该方法返回一个迭代器对象,迭代器对象是可以拥有.next()方法的任何对象,然后,在 for-of 的每次循环中,都将调用该迭代器对象上的 .next 方法,看下面一个简单的迭代器对象生成方法

var Itera = {
  [Symbol.iterator]: function () {
    return this;
  },
  next: function () {
    return {done: false, value: 0};
  }
};
复制代码

在上面代码中,每次调用.next()方法都返回了同一个结果,该结果一方面告知for-of语句循环还没有结束,另一方面告知for-of本次循环的值是0,这也就意味着上面方法是一个死循环,当然了,一个典型的迭代器绝对不会这么简单的 在ES6中,迭代器通过.done和.value这两个属性来标识每次的遍历结果,这就是迭代器的设计原理 现在我们知道了for-of的一些细节,那么我们可以简单的重写语句

for (VAR of ITERABLE) {
  代码内容
}
复制代码

上面只是一个语义化的实现,使用一些底层方法和几个临时变量

var iterator = ITERABLE[Symbol.iterator]();
var result = iterator.next();
while (!result.done) {
  VAR = result.value;
  STATEMENTS
  result = iterator.next();
}
复制代码

上面代码并没有涉及到如何调用.return()方法,我们可以添加相应的方法去处理。for-of 语句使用起来非常简单,但在其内部有非常多的细节值得我们去深入研究。

结束

ES6里面新增了很多很好玩又实用的特性,都值得我们去学习,for-of只是其中的一部分,还有更多的新特性可以去学习,今天就讲到这了。

你可能感兴趣的:(javascript,java,c#,ViewUI)