前言
遍历数组或对象是一名程序员的基本素养之一. 然而遍历却不是一件简单的事, 优秀的程序员知道怎么去选择合适的遍历方法, 优化遍历效率. 本篇将带你走进JavaScript遍历的世界, 享受分析JS循环的快感. 本篇所有代码都可以直接运行, 希望您通读本篇后, 不止是浏览, 最好是亲手去实践下.
概述
js有如下两种数据需要经常遍历
- 数组(Array)
- 对象(Object)
同时又提供了如下8种方法方便我们遍历元素
- for
- while(或do~while)
- forEach
- for in
- $.each
- $(selecter).each
- map
- every
最终我们将分析遍历效率选出最佳遍历选手.
本文将针对如下两种数据进行详细的分析和举栗. 下面举栗中如果不加特殊说明将会用到如下数据.
var array = ["囚徒","过客","领袖"];//职场3种人 var o = {0:"linda",1:"style",2:"nick",length:3};</div>
for
语法: for(初始化; 循环执行条件; 每遍历一个元素后做的事情;){}
(function(){//循环置于闭包之内 for(var i=0,length=array.length;i<length;i++){//缓存数组长度 console.log(array[i]);//内部方法若有可能相互影响,也要置于闭包之内 } })();</div>
for循环只能遍历数组, 不能遍历对象. 写for循环时有两点需要注意.
- 其一, 为了避免遍历时执行多遍计算数组长度的操作, 影响效率, 建议在循环开始以变量的形式缓存下数组长度, 若在循环内部有可能改变数组长度, 请务必慎重处理, 避免数组越界.
- JavaScript中并没有类似java的块级作用域, for循环内部定义的变量会直接暴露在外(如 i,循环退出后,i变量将等于数组长度, 后续代码将能访问到 i 变量的值), 因此建议将for循环置于闭包内. 特别要注意的是: 如果在循环内部, 前一个元素的遍历有可能影响到后一个元素的遍历, 那么for循环内部方法也需要置于闭包之内.
do/while
语法: do{...}while(true);
do while (function() { var i = 0, len = array.length; do { if (i == 2) { break; // 循环被终止, 此处如果是continue就会造成循环无法退出 }; console.log('array['+ i +']:' + array[i]); i++;//此句建议放置循环while头部 } while(i<len); })();</div>
do/while的语法简化了循环的实现, 只保留对循环条件的判断, 所以我们要在循环内部构造出循环退出的条件, 否则有可能造成死循环. 特别要注意的是: 使用 continue 跳出本次遍历时, 要保证循环能够自动进入到下一次遍历, 因此保证循环走到下一次遍历的语句需要放到 continue 前面执行, 建议置于循环头部.(如上, i++ 语句最好放置循环头部)
do/while 循环与for循环大体差不多,只支持数组遍历, 多用于对循环退出条件不是很明确的场景. 一般来说不建议使用这种方式遍历数组.
forEach
语法: array.forEach(function(item){})
, 参数item表示数组每一项的元素
array.forEach(function(item){ if(item=="囚徒") return;//这里只能使用return跳过当前元素处理 console.log(item); });</div>
forEach回调function默认有三个参数: item, index, array.
使用forEach循环有几点需要特别注意:
- forEach无法遍历对象
- forEach无法在IE中使用,只是在firefox和chrome中实现了该方法
- forEach无法使用break,continue跳出循环,使用return时,效果和在for循环中使用continue一致
for in
语法: for(var item in array){}
for(var item in array){ console.log(item); }//0 1 2 for(var item in o){ console.log(item); }//0 1 2 length</div>
for in 可用于遍历数组和对象, 但它输出的只是数组的索引和对象的key, 我们可以通过索引和key取到对应的值. 如下:
for(var item in array){ console.log(array[item]); }//"囚徒" "过客" "领袖" for(var item in o){ console.log(o[item]); }//"linda" "style" "nick" "length"</div>
$.each
语法: $.each(array|o, function(i, ele){})
支持数组和对象
$.each(array, function(i, ele){ console.log(i,ele,this==ele); }); //0 "囚徒" true //1 "过客" true //2 "领袖" true $.each(o, function(i, ele){ console.log(i,ele,this==ele); }); //0 "linda" true //1 "style" true //2 "nick" true</div>
这里我们注意到 this对象 指向当前属性的值,这是因为:
参考jQuery api:
$.each() 方法会迭代jQuery对象中的每一个DOM元素。每次回调函数执行时,会传递当前循环次数作为参数(从0开始计数)。更重要的是,回调函数是在当前DOM元素为上下文的语境中触发的。因此关键字 this 总是指向这个元素。
同时,上述遍历时, o 对象的属性中有一个length属性并没有被输出. 这是为什么呢? 请耐心往下看.
首先, 我们来看看遍历对象o时, 当前的this对象到底是什么?
$.each(o, function(i, ele){ if(this=="linda"){//我们随机选取第一个属性 console.log(this,this==ele); $.each(this, function(e, ele2) { console.log(e, ele2); }); } }); //String {0: "l", 1: "i", 2: "n", 3: "d", 4: "a", length: 5, [[PrimitiveValue]]: "linda"} true //0 "l" //1 "i" //2 "n" //3 "d" //4 "a"</div>
我们发现, this对象等于回调函数的第二个形参. 且它的 length 属性和 [[PrimitiveValue]] 属性并没有被打印出来, 为此我们来查看下length的内部属性.
$.each(o, function(i, ele){ if(this=="linda")//我们还是随机选取第一个属性(这还是随机吗?) console.log(Object.getOwnPropertyDescriptor(this, 'length')); }); //Object {value: 5, writable: false, enumerable: false, configurable: false}</div>
可见, this对象的length属性的 enumerable 属性被设置成了false, 这表示该对象不能被列举或遍历, 同时还不能被配置(configurable: false)
, 也不能被赋值(writable: false
) .
此时, 前面遍历 o 对象时,它的 length 属性没有被打印出来的疑问似乎有解了. 让我们来看看 o.length
的内部属性吧.
console.log(Object.getOwnPropertyDescriptor(o, 'length')); //Object {value: 3, writable: true, enumerable: true, configurable: true}</div>
o.length
值为3, 可赋值, 可列举, 可配置. 这可不对, 刚刚不是说 enumerable 属性被设置成了false 才不会被遍历吗. 现在该值为 true, 并且还不可遍历. 这不合常理, 自然该有别的原因. 我们接着往下看.
var o = {0:"linda",1:"style",2:"nick",length:1}; // 试着改变length的值 $.each(o, function(i, ele){//再遍历一次 console.log(i,ele); }); //0 "linda" var o = {0:"linda",1:"style",2:"nick",length:5}; // 坚持改变length的值 $.each(o, function(i, ele){//再遍历一次 console.log(i,ele); }); // 0 linda // 1 style // 2 nick // length 5 var o = {0:"linda",1:"style",2:"nick"}; // 试试去掉length属性 $.each(o, function(i, ele){//再遍历一次 console.log(i,ele); }); // 0 linda // 1 style // 2 nick</div>
现象明了, 结合jquery源码, 当对象中存在length属性时, $.each
内部使用for循环去遍历对象, 否则它将使用for in循环去遍历, 因此$.each
遍历对象遵循如下规律:
- 如果对象中存在 length 属性, 遍历深度以length属性为准, 即length多大, 遍历多少个元素.
- 如果对象中不存在 length 属性, 遍历深度以实际内部属性个数为准.
不仅如此, $.each
的具体使用过程中还有以下几点需要注意:
- 使用 return 或者 return true 为跳过一个元素,继续执行后面的循环;
- 使用 return false 为终止循环的执行, 这是因为在