写在前面
JavaScript中,茴香豆的'茴'有四种写法……开玩笑的,是数组的遍历有四种方法,本文将会分别展示四种遍历的用法,详细分析他们的区别并进行奇怪的测试以加深我们对各种遍历方式的了解(欢迎读者补充我没想到的测试用例),最后比较他们的性能,在此测试中我们得出了有趣的结论,本文略长,全是干货,望有耐心的旁友读到最后。
正文
遍历数组时,在所有语言中,最常用的方法都是
//以下简称为arr.length遍历 var arr = [0,1,2,3,4,5,6,7,8,9]; for(var i = 0; i < arr.length; i++){ console.log(arr[i]); }
用法举例
而在JS里, 对于数组的循环一共有四种,我们来分别看看他们的用法:
var arr = [1,2,3,4,5,6,7,8,9]; //1. arr.length遍历 for(var i = 0; i < arr.length; i++){ console.log("the index is:" + i); // 0,1,2,3,4,5,6,7,8 console.log("the value is:" + arr[i]); // 1,2,3,4,5,6,7,8,9 } //2. forEach遍历:idx对应数组下标, val对应arr[idx], arr对应arr arr.forEach(function(val,idx,arr){ console.log("the index is:" + idx); // 0,1,2,3,4,5,6,7,8 console.log("the value is:" + val); // 1,2,3,4,5,6,7,8,9 console.log("the length of the array is:" + arr.length); // 9,9,9,9,9,9,9,9,9 }); //3. for in遍历 for(var idx in arr ){ console.log("the index is:" + idx); // 0,1,2,3,4,5,6,7,8 console.log("the value is:" + arr[idx]); // 1,2,3,4,5,6,7,8,9 }; //4. for of遍历 for(var val of arr ){ console.log("the value is:" + val); // 1,2,3,4,5,6,7,8,9 };
用法分析&测试
1.用arr.length遍历:
实在是没什么好分析和测试的。
2. forEach遍历:
给forEach传一个用以回调的function作为入参,遍历时会被循环调用,function本身的入参按照顺序会是value,index和array本身。看到入参,敏锐的我们马上想到,如果我们在回调函数中改变index,val和array,会不会影响到数组本身的数据呢?实践出真知:
我们定义了一个包含js中所有类型的数组(当然[]和{}的类型其实是一样的,但是这里我们追求测试覆盖的广度),然后在回调函数中把idx和val全部设置为1,arr设置为[],然后运行,观察结果:
var arr = [100,"string",{},function(){},null,undefined,[]]; arr.forEach(function(idx,val,arr){ idx = 1; val = 1; arr = []; }); console.log(arr);
结果是:
[100, "string", {…}, ƒ, null, undefined, Array(0)]
得出结论,修改入参中的index,value和array并不会影响被循环的数组本身。
除此之外还要注意的是:forEach循环时,break并不能使你跳出循环,return也不会起作用。
3. for in遍历:
for in 遍历本身是为了遍历Object而设计的,由于[]的类型其实也是Object,所以也可以使用for in遍历[]。
但是要注意的是,for in遍历数组,看起来很优雅,但是要注意的坑很多!
让我们看这段代码(输出我直接标注在代码中了):
var arr = [1,2,3]; arr.selfProperty = 4; //用Object.getPrototypeOf(arr)获取arr的prototype,也可以使用Array.prototype代替 Object.getPrototypeOf(arr).protoPrototype = 5; for(var idx in arr){ console.log(typeof(idx)); //string string string string string console.log(idx); //0 1 2 selfProperty protoPrototype console.log(arr[idx]); //1 2 3 4 5 } console.log(arr); //(3) [1, 2, 3, selfProperty: 4] console.log(arr.length); //3
由输出我们可以看出内容非常丰富,一个一个看所有的坑:
1.所有的idx在遍历中类型都是string,所以如果进行index的加减运算,可能会出现奇怪的错误。
2.除了该有的index外,自己的属性(selfProperty)以及原型的属性(protoProperty)都被加入了遍历。
4. for of遍历:
for of 遍历和for in遍历类似,但是传入的是value而非index,话不多说,直接开始试验:
var arr = [1,2,3]; arr.selfProperty = 4; //用Object.getPrototypeOf(arr)获取arr的prototype,也可以使用Array.prototype代替 Object.getPrototypeOf(arr).protoPrototype = 5; for(var val of arr){ console.log(val); //1 2 3 } console.log(arr); //(3) [1, 2, 3, selfProperty: 4] console.log(arr.length); //3
可以看到for of除了没法得到index以外没什么坑,所以在能满足要求的情况下,尽量使用for of而非for in。
性能测试,生成10000000个随机数,然后放入数组中,将数组累加,查看花费时间,并将上述操作重复100遍,取平均结果:
1. arr.length遍历:
function test(times,size){ var results = []; for(var time = 0; time < times; time ++){ var length = size; var aTest = []; result = 0; for(var i = 0; i < length; i++){ aTest.push(Math.random()); } var dStart = new Date().getTime(); //要测试的循环体 for(var i = 0; i < aTest.length; i++){ result+=aTest[i]; } var dEnd = new Date().getTime(); results.push(dEnd - dStart); } return { results: results, avg: results.reduce(function(x,y){return x + y;}) / results.length }; } test(100,10000000)
结果是:
{results: Array(100), avg: 417.38} avg:417.38 results:(100) [417, 433, 437, 383, 448, 247, 282, 352, 415, 384, 381, 332, 427, 435, 427, 384, 428, 474, 430, 337, 422, 393, 430, 390, 425, 452, 390, 304, 439, 430, 477, 422, 448, 468, 385, 298, 464, 401, 430, 404, 465, 430, 468, 402, 456, 378, 418, 336, 460, 369, 470, 348, 440, 419, 473, 363, 459, 467, 420, 387, 423, 419, 401, 407, 428, 463, 489, 388, 469, 442, 466, 348, 463, 409, 457, 379, 453, 397, 475, 385, 487, 476, 457, 404, 477, 457, 468, 373, 446, 471, 263, 336, 376, 485, 441, 402, 500, 441, 499, 365]
平均:417.38ms。
2. forEach遍历:
function test(times,size){ var results = []; for(var time = 0; time < times; time ++){ var length = size; var aTest = []; result = 0; for(var i = 0; i < length; i++){ aTest.push(Math.random()); } var dStart = new Date().getTime(); //要测试的循环体 aTest.forEach(function(val,idx,arr){ result += val; }); var dEnd = new Date().getTime(); results.push(dEnd - dStart); } return { results: results, avg: results.reduce(function(x,y){return x + y;}) / results.length }; } test(100,10000000)
结果是:
{results: Array(100), avg: 510.66} avg:510.66 results:(100) [349, 458, 348, 377, 478, 470, 410, 450, 506, 351, 391, 446, 526, 609, 572, 553, 525, 390, 441, 562, 492, 455, 476, 546, 548, 556, 564, 459, 521, 451, 522, 488, 465, 533, 607, 584, 500, 507, 605, 508, 508, 444, 483, 515, 539, 574, 544, 454, 508, 525, 628, 453, 448, 539, 551, 548, 479, 435, 518, 488, 522, 566, 555, 619, 535, 565, 452, 477, 538, 552, 573, 476, 507, 540, 571, 567, 480, 515, 600, 534, 553, 454, 525, 510, 501, 561, 569, 561, 560, 551, 546, 509, 554, 519, 532, 444, 440, 510, 593, 550]
平均:510.66ms。
3.for in遍历function test(times,size){ var results = []; for(var time = 0; time < times; time ++){ var length = size; var aTest = []; result = 0; for(var i = 0; i < length; i++){ aTest.push(Math.random()); } var dStart = new Date().getTime(); //要测试的循环体 for(var idx in aTest){ result += aTest[idx]; } var dEnd = new Date().getTime(); results.push(dEnd - dStart); } return { results: results, avg: results.reduce(function(x,y){return x + y;}) / results.length }; } test(100,10000000)
结果是:
{results: Array(100), avg: 1670.5} avg:1670.5 results:(100) [1859, 1667, 1807, 1571, 1518, 1752, 1692, 1643, 1762, 1683, 1592, 1790, 1625, 1636, 1791, 1607, 1523, 1792, 1671, 1601, 1807, 1686, 1668, 1780, 1655, 1538, 1809, 1635, 1518, 1763, 1642, 1529, 1771, 1584, 1572, 1735, 1696, 1486, 1821, 1680, 1644, 1788, 1577, 1598, 1787, 1627, 1526, 1720, 1689, 1517, 1759, 1709, 1576, 1711, 1592, 1519, 1778, 1640, 1594, 1809, 1696, 1629, 1732, 1623, 1576, 1815, 1676, 1586, 1796, 1650, 1601, 1764, 1638, 1656, 1781, 1724, 1623, 1781, 1658, 1638, 1729, 1620, 1544, 1742, 1659, 1569, 1762, 1616, 1544, 1756, 1626, 1607, 1778, 1629, 1601, 1810, 1668, 1539, 1827, 1664]
平均:1670.5ms。
4.for of遍历
function test(times,size){ var results = []; for(var time = 0; time < times; time ++){ var length = size; var aTest = []; result = 0; for(var i = 0; i < length; i++){ aTest.push(Math.random()); } var dStart = new Date().getTime(); //要测试的循环体 for(var val of aTest){ result += val; } var dEnd = new Date().getTime(); results.push(dEnd - dStart); } return { results: results, avg: results.reduce(function(x,y){return x + y;}) / results.length }; } test(100,10000000)
结果:
{results: Array(100), avg: 509.85} avg:509.85 results:(100) [495, 509, 558, 507, 527, 524, 505, 541, 482, 484, 495, 477, 483, 479, 491, 500, 485, 490, 486, 498, 519, 506, 494, 455, 522, 465, 502, 501, 473, 484, 474, 489, 500, 492, 480, 496, 485, 503, 518, 526, 491, 506, 522, 546, 515, 491, 546, 481, 550, 493, 479, 549, 511, 495, 504, 546, 604, 507, 542, 502, 598, 501, 533, 502, 485, 466, 537, 465, 489, 518, 575, 552, 482, 476, 476, 588, 498, 490, 564, 539, 548, 508, 508, 520, 526, 482, 506, 577, 498, 515, 484, 549, 574, 566, 466, 530, 519, 489, 485, 521]平均:509.85ms。
结果汇总:
arr.length: 417.38ms。
forEach: 510.66ms。
for in: 1670.5ms。
for of: 509.85ms。
最基础也最常见的用length进行循环效率最高,forEach和for of持平比length循环慢了20%,而for in的耗时是length循环的四倍!
最终得出结论,length循环还是最好的循环,灵活且效率高,在某些需求下也可以用forEach和for of,会稍微慢一些但是也还可以接受,但是出于避坑和效率的考虑,还是少用for in来循环数组为好!