JavaScript 实现循环遍历的方法总结 以及 常用方法性能比较( Duff‘s Device)

·  阅读 678
JavaScript 实现循环遍历的方法总结 以及 常用方法性能比较( Duff‘s Device)

一、实现循环遍历的方法

原生JS实现遍历:

1. While

While 语句包括一个循环条件和一段代码块,只要条件为真,就不断循环执行代码块。

while (条件) { 执行代码; }; 
复制代码

2. do...While

do...while 循环与 while 循环类似,唯一的区别就是先运行一次循环体,然后判断循环条件。

do { 
    执行代码; 
} while (条件);
复制代码

3. for

普通 for 循环,经常用的数组遍历。使用临时变量,将长度缓存起来,避免重复获取数组长度,当数组较大时优化效果才会比较明显。

for(j = 0; j < arr.length; j++) {
    // 代码执行
}
复制代码

4. for ... in

for…in 循环一般用于对象的遍历,不适用于遍历数组。

但是这里有一个坑需要注意: 任何对象都继承了Object对象,或者其它对象,继承的类的属性是默认不可遍历的,for... in 循环遍历的时候会跳过,但是这个属性是可以更改为可以遍历的,那么就会造成遍历到不属于自身的属性。

var obj = {a: 1, b: 2, c: 3}; 

for (var i in obj) { 
    console.log('键名:', i); 
    console.log('键值:', obj[i]); 
} 
// 键名:a 键值:1 键名:b 键值:2
// 其中 obj为循环的对象, i 为对象中的“键名”。如果对象是数组,那么i就是索引。
复制代码

如果继承的属性是可遍历的,那么就会被for...in循环遍历到。但如果只想遍历自身的属性,使用for...in的时候,应该结合使用hasOwnProperty() 方法,在循环内部判断一下,某个属性是否为对象自身的属性。否则就可以产生遍历失真的情况。

遍历数组的缺点:数组的下标 index 值是数字,for-in遍历的 index值"0","1","2"等是字符串。

5. for ... of

for...of 遍历 是ES6新增功能

for( let i of arr){
    console.log(i);
}
复制代码

for...of 循环不仅支持数组,还支持大多数类数组对象,也支持字符串遍历。 for...of 它可以正确响应 breakcontinuereturn 语句。

6. forEach

ES5推出的,数组自带的循环,主要功能是遍历数组,实际性能比for还弱。

arr.forEach(function(item, index){
  console.log('forEach遍历:' + index + '--' + item);
})
复制代码

forEach 不能使用 break 语句中断循环,也不能使用 return 语句返回到外层函数。

7. map()

map() 方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。

arr.map(function(value, index){
    console.log('map遍历:' + index + '--' + value);
});	 
// map遍历支持使用return语句,支持return返回值
var temp = arr.map(function(val, index){
  console.log(val); 
  return val * val;          
})
console.log(temp);
复制代码

注意:map() 是返回一个新数组,而不会改变原数组。

8. filter()

filter() 方法用于过滤数组成员,满足条件的成员组成一个新数组返回。它的参数是一个函数,所有数组成员依次执行该函数,返回结果为true的成员组成一个新数组返回。该方法不会改变原数组。

var arr = [73, 84, 56, 22, 100];
var newArr = arr.filter(item => item>80)
// 得到新数组 newArr = [84, 100],原数组arr没有改变。
复制代码

9. every()

every() 是对数组中的每一项运行指定函数来判断数组成员是否符合某种条件,如果该函数对每一项返回true,则返回true。

var arr = [ 1, 2, 3, 4, 5, 6 ]; 
arr.every( function( item, index, array ){ 
    return item > 3; 
}); 
// false
复制代码

10. some()

some() 与上面得 every() 相反,如果该函数对任一项返回 true,则整个 some() 方法的返回值就是 true,否则返回 false。

var arr = [ 1, 2, 3, 4, 5, 6 ]; 
arr.every( function( item, index, array ){ 
    return item > 3; 
}); 
// true
复制代码

两者比较,some() 只要有一个是 true,便返回 true;而 every() 只要有一个是 false,便返回 false。

11. reduce() 和 reduceRight()

reduce() 方法和 reduceRight() 接收一个函数作为累加器,函数有四个参数,分别是:上一次的值,当前值,当前值的索引,数组。 它们的差别是,reduce() 是从左到右处理(从第一个成员到最后一个成员),reduceRight() 则是从右到左(从最后一个成员到第一个成员),其他完全一样。

var total = [0, 1, 2, 3, 4];
// 这四个参数之中,只有前两个是必须的,后两个则是可选的。
total.reduce(function( previousValue, currentValue){
    return previousValue + currentValue;
});
//  如果要对累积变量指定初值,可以把它放在reduce方法和reduceRight方法的第二个参数。
total.reduce(function( a, b){
    return a + b;
}, 10);
// 上面的第二个参数相当于设定了默认值,处理空数组时尤其有用,可避免一些空指针异常。
复制代码

12. find()

find() 方法返回数组中符合函数条件的第一个元素。否则返回 undefined

var stu = [{ name: '张三', }, { name: '王小毛', }, { name: '李四', age: 18}]
stu.find((item) => (item.name === '李四')) 
// {name: "李四", age: 18}
复制代码

13. findIndex()

对于数组中的每个元素,findIndex() 方法都会调用一次回调函数(采用升序索引顺序),直到有元素返回 true。只要有一个元素返回 true,findIndex() 立即返回该返回 true 的元素的索引值。如果数组中没有任何元素返回 true,则 findIndex() 返回 -1。

findIndex() 不会改变数组对象。

[1,2,3].findIndex(x => x === 2 );  // 1
[1,2,3].findIndex(x => x === 4 );  // -1
复制代码

14. ES6 新增:entries(),keys() 和 values()

entries()keys()values() —— 用于遍历数组。它们都返回一个遍历器对象,可以用 for...of 循环进行遍历。

唯一的区别是 keys() 是对 键名 的遍历、values()是对 键值 的遍历,entries() 是对 键值对 的遍历。

// keys() 键名 的遍历
for (let i of ['a', 'b'].keys()) {
    console.log(i);  // 0  1
}
// values() 键值 的遍历
for (let item of ['a', 'b'].values()) {
    console.log(item);  // a  b
}
// entries() 键值对 的遍历
for (let [index, item] of ['a', 'b'].entries()) {
    console.log(index, item);
    // 0 "a"    1 "b"
}
复制代码

jQuery实现遍历:

1. jQuery.grep()

$.grep() 函数使用指定的函数过滤数组中的元素,并筛选符合条件的元素,组成新的数组,并返回。

var arr = [ 1, 9, 3, 8, 6, 1, 5, 9, 4, 7, 3, 8, 6, 9, 1 ];
arr = jQuery.grep(arr, function( item, index ) {
// function 中两个参数,一是当前迭代的数组元素,二是当前迭代元素在数组中的索引。
     if( item !== 5 && index > 4){
       // 返回元素不为5,且索引大于4的数组
       return true;
      }
});
console.log(arr) // (9) [1, 9, 4, 7, 3, 8, 6, 9, 1]
复制代码

2. jQuery.each()

$.each() 函数用于遍历指定的对象和数组。

jQuery.each([52, 97], function(index, value) {
    console.log(index + ': ' + value);
    // 0: 52   // 1: 97
});
复制代码

3. jQuery.inArray()

$.inArray() 函数在数组中查找指定值,并返回它的索引值(如果没有找到,则返回-1)。

var anArray = ['one', 'two', 'three'];
var index = $.inArray('two', anArray);
console.log(index); //返回该值在数组中的键值,返回1
console.log(anArray[index+1]); // three
复制代码

4. jQuery.map()

$.map() 函数用于使用指定函数处理数组中的每个元素(或对象的每个属性),并将处理结果封装为新的数组返回。

$(function () { 
    var arr = ['0','1','2','3','4','S','6'];
    var values = $.map(arr, function(value){
    var result = new Number(value);
        return isNaN(result) ? null : result; // 如果不是NaN就返回result值
    });
    // 遍历打印新返回的values
    for (key in values) {
        console.log(values[key]) // 0, 1, 2, 3, 4, 6
    }
})
复制代码

二、常用循环遍历方法性能比较

在这里主要对以下几个常用方法进行性能方面的比较:正常的 for 循环、倒序 for 循环、while 循环、for-in 循环、for each 循环 和 Duff's Device 循环。

即使是循环中最快的代码,累计迭代上千次也会慢下来。此外,循环体运行时也会带来小性能开销,不仅仅是增加了总体运行时间。减少迭代次数能获得更加显著的性能提升,最广为人知的一种限制循环迭代次数的模式被称为“达夫设备(Duff's Device)”。

var num = 10000;  // 数组大小
var itemNum = 1000;  // 迭代次数  100/1000/10000/100000
var array = [];  // 初始化数组
for (var i = 0 ; i < num ; i++ ){
    array[i] = i + 1;
}
var len = array.length;

console.log("迭代次数: " + itemNum);

// 正常for循环
console.time('正常for循环');
for(var j = 0 ; j < itemNum ; j ++) {
    for(var k = 0 ; k < len ; k ++) {
        array[k] + 1;
    }
}
console.timeEnd('正常for循环');


// 倒序for循环
console.time('倒序for循环');
for(let j = 0; j < itemNum; j ++){
    for(let k = len - 1 ; k --;)
    {
        array[k] + 1;
    }
}
console.timeEnd('倒序for循环');


// while循环
console.time('while循环');
for(let j = 0; j < itemNum; j ++){   
    let k = 0;
    while(k < len){
        array[k] + 1;
        k ++;
    }
}
console.timeEnd('while循环');


// for-in循环
console.time('for-in循环');
for(let j = 0; j < itemNum; j ++){
    for(let k in array){
        array[k] + 1;
    }
}
console.timeEnd('for-in循环');


// for each 循环
console.time("for-each循环");
for(let j =0; j <itemNum; j ++){
    array.forEach((item) => {
        item + 1;
    });
}
console.timeEnd("for-each循环");


// Duff's Device 算法是一种循环体展开技术,它使得一次迭代中实际执行了多次迭代的操作。
console.time("Duff's Device");
for(let k = 0; k < itemNum; k ++){
    let j = len % 8;
    let tempLen = len-1;
    while(j){
        j--;
        array[tempLen--] + 1;
    }
    j = Math.floor(len / 8);
    while(j){
        j--;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
        array[tempLen --] + 1;
    }
}
console.timeEnd("Duff's Device");
复制代码

通过每次循环遍历,打印每种方式的运行时间,可以发现:

  • 总体来说 for 循环和 for-each 性能相当,在小数据量时也与 while 性能相当。for-in 性能最差,duff's device 性能最好。大数据量时,while 性能优于 for 循环和 for-each 循环。
  • 对于数据量不大的循环(<1000),不用考虑使用哪一种性能更好,可读性是第一位。
  • 对于有较大数据量的循环和遍历,如果性能不是瓶颈,那么普通的 for 循环(或者 while,do-while,for-each)就可以了,毕竟可读性强。
  • 对于较大的数据量,如果 Array 的循环操作已经成为瓶颈,或者性能非常重要,那么可以采用 duff's device 方案。
    • Duff's device 背后的基本理念是:每次循环中最多可8次调用执行函数。循环迭代次数为元素总数除以8,因为总数不一定是8的整数倍,所以声明变量 j 存放余数,指出第一次循环中应当执行多少次。比方说现在有12个元素,那么第一次循环将调用执行函数4次,第二次循环调用执行函数8次,用2次循环代替了12次循环。
    • 如何使用该方案,很大程度上依赖于迭代的次数。如果循环迭代次数少于1000次,你可能只看到它与普通循环相比只有微不足道的性能提升。如果迭代次数超过1000次,Duff's device 的效率明显提升。例如500000次迭代中,运行时间比普通循环减少到70%。

参考文章: www.cnblogs.com/libin-1/p/6… blog.csdn.net/cengjingcan…

分类:
前端
标签: