遍历算法在我们日常编程过程中算是使用得最多的了。这里我们就简单的聊聊,在JS中关于各种基本数据结构的遍历方式以及其相关的坑。
字符串的遍历
- for循环
在以前的JS中,并没有直接遍历字符串的函数,我们遍历一个字符串的话,需要这样做:
1 |
|
- for in遍历
取到字符串的长度,然后通过for循环,用charAt函数来定位到具体的位置。同样的方式还有for in :
1 |
|
但是这种方式虽然可行,可是又用了遍历索引,又用了字符串方法,感觉不够优雅。
既然说到了for in方法,那么就说说for in中得注意得地方: 在for(var i in str) 语句中,你所使用的i和for(var i = 0; i<str.length,i++)中的i是不一样的。前者是字符串类型,后者是number类型,这就需要我们引起重视,有时候我们会要做索引与变量的求和操作,这个时候如果索引是字符串类型就会引起一些很小很隐蔽的错误。
- for of遍历
于是在ES6中,最最万金油的遍历方法诞生:
1 |
|
看看关于MDN中对for of 方法的描述:
for…of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
for of可以遍历所有的基本数据类型,但是却无法遍历一般的对象。
1 | var obj = {"name":"sa","age":25} |
对于这样的对象,我们还是统一用for in吧。
数组的遍历
- for 循环
看到遍历数组,大家最先想到的肯定是for循环:
1 |
|
- while 循环
完全没有问题,这是最最common的遍历方式,但是也有人用while:
1 |
|
要是有人在我的代码里这样用while写遍历,我百分之一百会打死他,除非他是我头儿,哈哈。
while在js里来说还是需要非常注意以及小心使用的循环方法,如果稍有不慎,在跳出条件上没有做好控制,就能秒秒钟让你的web页面崩溃,卡到动不了,而且,更厉害的是,如果你用了很多这样的遍历方法,你甚至连错误都很难找到,控制台经常内存泄漏死掉连错误信息都没有,所以,上面的while遍历方法,在我看来,是很不安全的,尽量少用。
- forEach 循环
forEach是ES5中发布的数组循环的方法,我们来看看:
1 |
|
看上去还是挺不错的,能成功的将值和索引都遍历出来,但是也有致命的缺陷。那就是forEach循环中无法使用跳出循环的语句,如:break。这样就导致forEach的使用场景被大大的限制了。
- for in 循环
当然,遍历数组也是可以用到for in的:
1 |
|
完全没有问题,for in要小心的地方我已经在字符串遍历的时候说了,上述代码中的i都是字符串。
- map 和 filter 遍历
这两个方法多是用在对数组元素的操作上,但是有很多同学可能都没有使用过。这里我们先看看MDN上关于两者的介绍:
map:
map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
filter:
filter 为数组中的每个元素调用一次 callback 函数,并利用所有使得 callback 返回 true 或 等价于 true 的值 的元素创建一个新数组。callback 只会在已经赋值的索引上被调用,对于那些已经被删除或者从未被赋值的索引不会被调用。那些没有通过 callback 测试的元素会被跳过,不会被包含在新数组中。
其实这两个函数在使用的方法上还是挺相似的,都是通过遍历数组中的没个元素,然后对其执行相应的函数,然后将结果返回到一个新的数组中去。
我们来看看用这种方式的遍历:
map:
1 |
|
我们在map函数中不仅可以遍历每个数组对象,还能对其操作返回新的数组,而且新数组与原来得数组相互不影响。这是其很棒得一个特性,解决了一个数组浅拷贝的问题。
filter方法的使用也是类似的,只是在返回新数组的时候filter有一个判别条件,用来返回符合要求的结果。也算是如其名吧。
filter:
1 |
|
- for of
最后放上来的是ES6中的for of,除了对象类型,什么都可以遍历的万金油方法。
1 |
|
JS对象的遍历
- for in
1 |
|
首先列出来的是最最常用的for in循环。前面已经做过了介绍,这里就不再重复了。
- Object.keys和 forEach的组合方式
1 |
|
这是通过Object.keys方法来获取对象的key数组,然后使用forEach遍历该数组,最后遍历出对象所有value的方法。类似的组合方式还有一些,我就不举例说明了,原理都和上述方式一致。
关于for循环中的闭包所引起的索引问题
1 |
|
很多人预期的结果是0 1 2 ,但是最后的结果是3 3 3。
造成这个结果的原因是因为i的变量升级,当我们在for循环的时候执行list.push时,此时的函数只是一个声明,并没有将i的值确定,而在我们执行list0这样的函数时,程序才会开始去查找这个i的值,而此时的for循环早已经结束了,i从for循环中块级变量提升到了全局变量(js中没有块级作用域),最后停到了3的位置,于是,不论是list[0],还是list[1],list[2]所能取到的i都只有最后一个3(不要问我为什么是3不是2)。当然,对这个问题,你只要这样理解就可以了,至于更底层的原因,如果你有兴趣就自己去网上搜一搜,我就不展开讲了。
解决的办法:
1 |
|
为什么加了一个闭包就可以了呢?
因为这里的闭包是一个立即执行的函数,每一次的运行,i作为参数传进去,函数中的i值立马就确定了,不需要等到最后调用的时候才去查找这个i的值(确定的 0 1 2)。
不过在ES6中有了更棒的解决办法。由于ES6引入了新的声明关键字let,而let又是属于块级作用域的,所以
1 |
|
当执行list[0]()时,i的值不会再从全局作用域里取到了,而是从存在的块级作用域中正确拿到。所以,在for循环中使用let时,不需要引入闭包也能解决变量升级的问题了。
结尾
ok,关于js的遍历就讲到这里,关于遍历中的坑我也简单的提了一下,希望能对不了解的同学有所帮助。