整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript 各种遍历方式详解

JavaScript 各种遍历方式详解

了方便例子讲解,现有数组和字面量对象如下

var demoArr=['Javascript', 'Gulp', 'CSS3', 'Grunt', 'jQuery', 'angular'];
var demoObj={
  aaa: 'Javascript',
  bbb: 'Gulp',
  ccc: 'CSS3',
  ddd: 'Grunt',
  eee: 'jQuery',
  fff: 'angular'
};

for

可以直接看示例,用得太多了,很简单

(function () {
  for (var i=0, len=demoArr.length; i < len; i++) {
    if (i==2) {
      // return;   // 函数执行被终止
      // break;    // 循环被终止
      continue; // 循环被跳过
    };
    console.log('demo1Arr[' + i + ']:' + demo1Arr[i]);
  }
})();

关于for循环,有以下几点需要注意

  • for循环中的 i 在循环结束之后任然存在于作用域中,为了避免影响作用域中的其他变量,使用函数自执行的方式将其隔离起来()();
  • 避免使用 for(var i=0; i<demo1Arr.length; i++){} 的方式,这样的数组长度每次都被计算,效率低于上面的方式。也可以将变量声明放在for的前面来执行,提高阅读性
var i=0, len=demo1Arr.length;
for(; i<len; i++) {};
  • 跳出循环的方式有如下几种 return 函数执行被终止 break 循环被终止 continue 循环被跳过

for in

for(var item in arr|obj){} 可以用于遍历数组和对象

  • 遍历数组时,item表示索引值, arr表示当前索引值对应的元素 arr[item]
  • 遍历对象时,item表示key值,arr表示key值对应的value值 obj[item]
(function () {
  for (var i in demoArr) {
    if (i==2) {
      return; // 函数执行被终止
      // break;  // 循环被终止
      // continue;  // 循环被跳过
    };
    console.log('demoArr[' + i + ']:' + demoArr[i]);
  }
  console.log('-------------');
})();

for in 本质上遍历的是对象,之所以能遍历数组,是因为数组也是一个对象。

var arr=['react', 'vue', 'angular'];

// 等价于

var arr={
  0: 'react',
  1: 'vue',
  2: 'angular'
}

关于for in,有以下几点需要注意:

  • 在 for 循环与 for in 循环中,i 值都会在循环结束之后保留下来。因此使用函数自执行的方式避免。
  • 使用 return,break,continue 跳出循环都与 for 循环一致,不过关于 return 需要注意,在函数体中,return 表示函数执行终止,就算是循环外面的代码,也不再继续往下执行。而 break 仅仅只是终止循环,后面的代码会继续执行。
function res() {
  var demoArr=['Javascript', 'Gulp', 'CSS3', 'Grunt', 'jQuery', 'angular'];

  for (var item in demoArr) {
    if (item==2) {
      return;
    };
    console.log(item, demoArr[item]);
  }
  console.log('desc', 'function res'); //不会执行
}

因为 for in 的目的是为了遍历对象,因此在遍历时,会同时搜索该对象构造函数上的属性以及原型上的属性,因此 for in 循环相对来说消耗会更大一点。因此,如果有其他更好的选择,则尽量避免考虑使用 for in 循环来遍历数据。

forEach

demoArr.forEach(function(arg) {})

参数arg表示数组每一项的元素,实例如下

demoArr.forEach(function (val, index) {
  if (e=='CSS3') {
    return;  // 循环被跳过
    // break;   // 报错
    // continue;// 报错
  };
  console.log(val, index);
})

具体有以下需要注意的地方

  • 回调函数中有2个参数,分别表示值和索引,这一点与 jQuery 中的$.each相反
  • forEach无法遍历对象
  • forEach无法在IE中使用,firefox和chrome实现了该方法
  • forEach无法使用 break,continue 跳出循环,使用 return 时,效果和在 for 循环中使用 continue 一致

ES5中新增的几个数组方法,forEach, map, filter, reduce等,可以理解为依次对数组的每一个子项进行一个处理(回调函数中的操作),他们是对简单循环的更高一层封装,因此与单纯的循环在本质上有一些不同,所以才会导致 return, continue, break 的不同。

最重要的一点,可以添加第二参数,为一个数组,而且回调函数中的this会指向这个数组。而如果没有第二参数,则this会指向window。

var newArr=[];
demoArr.forEach(function(val, index) {
  this.push(val); // 这里的this指向newArr
}, newArr)

虽然在原生中 forEach 循环的局限性很多,但是了解他的必要性在于,很多第三方库会扩展他的方法,使其能够应用在很多地方,比如 angular 的工具方法中,也有 forEach 方法,其使用与原生的基本没有差别,只是没有了局限性,可以在IE下使用,也可以遍历对象

var result=[];
angular.forEach(demoArr, function(val, index) {
  this.push(val);
}, result);

do/while

函数具体的实现方式如下,不过有一点值得注意的是,当使用 continue时,如果你将 i++ 放在了后面,那么 i++ 的值将一直不会改变,最后陷入死循环。因此使用do/while一定要小心谨慎一点。

// 直接使用while
(function () {
  var i=0,
    len=demoArr.length;
  while (i < len) {
    if (i==2) {
      // return; // 函数执行被终止
      // break;  // 循环被终止
      // continue;  // 循环将被跳过,因为后边的代码无法执行,i的值没有改变,因此循环会一直卡在这里,慎用!!
    };
    console.log('demoArr[' + i + ']:' + demoArr[i]);
    i++;
  }
  console.log('------------------------');
})();

// do while
(function () {
  var i=0,
    len=demo3Arr.length;
  do {
    if (i==2) {
      break; // 循环被终止
    };
    console.log('demo2Arr[' + i + ']:' + demo3Arr[i]);
    i++;
  } while (i < len);
})();

不建议使用do/while的方式来遍历数组

$.each

$.each(demoArr|demoObj, function(e, ele))
可以用来遍历数组和对象,其中e表示索引值或者key值,ele表示value值

$.each(demoArr, function(e, ele) {
  console.log(e, ele);
})

输出为

0 "Javascript"
1 "Gulp"
2 "CSS3"
3 "Grunt"
4 "jQuery"
5 "angular"

这里有很多需要注意的地方

  • 使用return 或者return true为跳过一次循环,继续执行后面的循环
  • 使用return false为终止循环的执行,但是并不终止函数执行
  • 无法使用break与continue来跳过循环
  • 循环中this值输出类似如下
console.log(this);
//String {0: "C", 1: "S", 2: "S", 3: "3", length: 4, [[PrimitiveValue]]: "CSS3"}

console.log(this==ele);
// true
  • 关于上面的this值,遍历一下
$.each(this, function(e, ele) {
  console.log(e, ele);
})

// 0 c
// 1 s
// 2 s
// 4 3

为什么 length 和 [[PrimitiveValue]]没有遍历出来?突然灵光一动,在《javascript高级编程》中找到了答案,大概意思就是javascript的内部属性中,将对象数据属性中的 Enumerable 设置为了false

// 查看length的内部属性
console.log(Object.getOwnPropertyDescriptor(this, 'length'));
// Object {value: 4, writable: false, enumerable: false, configurable: false}

(this)` 与this有所不同,不过遍历结果却是一样,你可以在测试代码中打印出来看看

$(selecter).each

专门用来遍历DOMList

$('.list li').each(function (i, ele) {
  console.log(i, ele);
  // console.log(this==ele); // true
  $(this).html(i);
  if ($(this).attr('data-item')=='do') {
    $(this).html('data-item: do');
  };
})
  • i: 序列值 ele: 只当前被遍历的DOM元素
  • this 当前被遍历的DOM元素,不能调用jQuery方法
  • (ele) 当前被遍历元素的jquery对象,可以调用jquery的方法进行dom操作

使用for in 遍历 DOMList

因为domList并非数组,而是一个对象,只是因为其key值为0,1,2... 而感觉与数组类似,但是直接遍历的结果如下

var domList=document.getElementsByClassName('its');
for(var item in domList) {
  console.log(item, ':' + domList[item]);
}
// 0: <li></li>
// 1: <li></li>
//    ...
// length: 5
// item: function item() {}
// namedItem: function namedItem() {}

因此我们在使用for in 遍历domList时,需要将domList转换为数组

var res=[].slice.call(domList);
for(var item in res) {}

类似这样的对象还有函数的属性 arguments 对象,当然字符串也是可以遍历的,但是因为字符串其他属性的 enumerable 被设置成了false,因此遍历出来的结果跟数组是一样的,也就不用担心这个问题了.

for of

for of 用于遍历可迭代对象「Iterator」。在 JS 中,数组 Array,字符串 String, Map,Set 等,都是可迭代对象。

对象中包含 Symbol.iterator 属性的,都被称为可迭代对象。

var arr=[1, 2, 3];
arr[Symbol.iterator]
// ? values() { [native code] }

简单案例。

const iterable=['react', 'vue', 'angular'];
 
for (const value of iterable) {
  console.log(value);
}
  • for of 仅仅针对可迭代对象
  • 跳出循环的方式与 for 循环保持一致

小补充

如果你发现有些人写函数这样搞,不要惊慌,也不要觉得他高大上鸟不起

+function(ROOT, Struct, undefined) {
  ... 
}(window, function() {
    function Person() {}
})

()(), !function() {}() +function() {}() 三种函数自执行的方式

学习是一个艰苦的过程,当然如果能把技术学成,最后也一定可以获得高薪工作。掌握一个好的学习方法,跟对一个学习的人非常重要。今后要是大家有啥问题,可以随时来问我,能帮助别人学习解决问题,对于自己也是一个提升的过程。自己整理了一份2020最全面前端学习资料,从最基础的HTML+CSS+JS到HTML5的项目实战的学习资料都有整理web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取

JavaScript 作为 Web 开发的核心语言之一,在前端领域发挥着至关重要的作用。数组作为 JavaScript 中最常用的数据结构之一,掌握其遍历方法对于任何前端开发者都是必不可少的技能。本文旨在介绍几种常见的数组遍历方式,并通过实例演示它们的应用场景和最佳实践。

技术概述

定义

数组遍历是指按照一定的顺序访问数组中的每一个元素的过程。JavaScript 提供了多种方法来遍历数组,包括传统的 for 循环、forEach 方法、mapfilter 等高阶函数。

核心特性与优势

  • 传统 `for` 循环:
  • 特性: 直接访问索引。
  • 优势: 灵活性高,可以控制循环的终止条件。
  • `forEach` 方法:
  • 特性: 自动迭代数组中的每个元素。
  • 优势: 代码简洁,易于理解和维护。
  • `map` 方法:
  • 特性: 创建新数组,对原数组每个元素执行函数并返回新值。
  • 优势: 方便转换数据结构。
  • `filter` 方法:
  • 特性: 创建新数组,包含通过测试的所有元素。
  • 优势: 用于筛选数据。

示例代码

const numbers=[1, 2, 3, 4, 5];

// 使用 for 循环
for (let i=0; i < numbers.length; i++) {
  console.log(numbers[i]);
}

// 使用 forEach
numbers.forEach(function(number) {
  console.log(number);
});

// 使用 map
const doubled=numbers.map(function(number) {
  return number * 2;
});
console.log(doubled);

// 使用 filter
const evenNumbers=numbers.filter(function(number) {
  return number % 2===0;
});
console.log(evenNumbers);

技术细节

遍历原理

数组遍历的基本原理是按照一定的顺序访问数组中的每个元素。不同的遍历方法内部实现有所不同,但最终目的是相同的。

  • `for` 循环:
  • 原理: 通过索引直接访问数组元素。
  • 注意事项: 索引越界问题。
  • `forEach` 方法:
  • 原理: 内部使用迭代器模式。
  • 注意事项: 无法中断遍历过程。
  • `map` 方法:
  • 原理: 对每个元素应用函数并返回新数组。
  • 注意事项: 不改变原数组。
  • `filter` 方法:
  • 原理: 对每个元素应用函数判断是否保留。
  • 注意事项: 同样不改变原数组。

难点

  • 中断遍历: forEach 方法不能直接使用 breakreturn 中断遍历。
  • 性能考虑: mapfilter 返回新数组,可能导致内存消耗增加。

实战应用

场景与案例

假设我们需要从一个数组中找出所有的偶数,并计算这些偶数的平方。

问题与解决方案

问题: 如何高效地找到所有偶数并计算它们的平方?

解决方案: 使用 filter 方法筛选出偶数,再使用 map 方法计算平方。

示例代码

const numbers=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 使用 filter 和 map
const evenSquares=numbers
  .filter(number=> number % 2===0)
  .map(number=> number * number);

console.log(evenSquares); // 输出: [4, 16, 36, 64, 100]

优化与改进

潜在问题

  • 性能: 在大数据量下使用 mapfilter 可能导致性能下降。
  • 内存: 过多使用高阶函数可能会增加内存占用。

优化建议

  • 减少循环次数: 尝试合并操作以减少循环次数。
  • 使用缓存: 如果结果不变,可以缓存结果以减少重复计算。

示例代码

// 缓存结果
const cache={};

function getEvenSquares(numbers) {
  if (cache[numbers]) {
    return cache[numbers];
  }

  const result=numbers
    .filter(number=> number % 2===0)
    .map(number=> number * number);

  cache[numbers]=result;

  return result;
}

常见问题

问题及解决

问题: 如何在 forEach 中中断循环?

解决方案: 使用 for 循环或 Array.prototype.some() 方法。

// 使用 some
const numbers=[1, 2, 3, 4, 5];
let found=false;

numbers.some(function(number) {
  if (number > 3) {
    found=true;
    return true; // 终止循环
  }
});

console.log(found); // 输出: true

总结与展望

通过本文的学习,我们了解了 JavaScript 数组遍历的多种方法及其应用场景。每种方法都有其特点和适用场合,合理选择可以提高代码的效率和可读性。随着 JavaScript 语言的发展,新的遍历方法和优化技巧会不断出现,我们期待未来能有更多实用高效的工具和技术出现,以进一步提升前端开发的效率和质量。

希望本文能为你在实际开发中遇到的问题提供一些解决方案和思路,同时也鼓励你不断学习新的技术和方法,以应对日益复杂的前端开发挑战。

、for...in 语句


1.1 遍历对象属性名称

for...in 语句常用于遍历特定对象的属性,包括字段名称属性及函数名称属性。在JavaScript语言中,它们均称为属性 (property)。

let obj={
  name: 'obj',

  showName() {
    console.log(this.name);
  }
};

for (const propName in obj) {
  console.log(propName, typeof(obj[propName]));
}

显示:

name – "string"
showName – "function"

利用这一点,可方便地查看特定对象的所有属性或方法名称。下面语句打印出console对象所有的属性:

for (const propName in console) {
  console.log(propName, typeof(console[propName]));
}

显示:

debug – "function"
error – "function"
log – "function"
info – "function"
warn – "function"
clear – "function"
...

可以看出,这些属性全部均是console的函数名称,因为console没有属性名称。

1.2 遍历数组索引值

for...in 用于数组中,则遍历数组索引值。

let array=['a', 'b', 'c'];

for (const index in array) {
  console.log(index, array[index]);
}

显示:

0 – "a"
1 – "b"
2 – "c"

1.3 遍历字符串索引值

由于字符串是由字符组成的数组,因此 for...in 也可用于遍历字符串的索引值。

let str="abc";

for (const index in str) {
  console.log(index, str[index]);
}

显示:

0 – "a"
1 – "b"
2 – "c"

2、for...of 语句


for...of 语句用于遍历可遍历对象 (iterable objects)的元素。这些可遍历对象包括字符串、数组、以及类似于数组的对象,这些对象都带有length属性。

2.1 不能用于遍历对象属性名称

for...of 语句不能用于遍历对象的属性名称。因此,下面的代码是错误的:

let obj={
  name: 'obj',

  showName() {
    console.log(this.name);
  }
};

for (const propName of obj) {
  console.log(propName);
}

显示:

TypeError: undefined is not a function (near '...propName of obj...')

意为,将 for...of 语句用于对象上面,无法提取具体的数值。

2.2 遍历数组元素

for...of 语句经常用于遍历数组中各元素的数值。

let arr=[2, 4, 6, 8, 10];

for (const value of arr) {
  console.log(value);
}

显示:

2
4
6
8
10

2.3 遍历字符串中的字符

由于字符串是由字符组成的数组,因此 for...of 也可用于遍历字符串的字符。

let str="abc";

for (const letter of str) {
  console.log(letter);
}

显示:

a
b
c

2.4 解包

数组元素如果是带有特定属性名称的对象,可利用解包性质来快速遍历这些属性值。看下面例子。

function Point(x, y) {
  return {x:x, y:y};
}

let points=[Point(1, 2), Point(2, 3), Point(4, 5)];

for (const point of points) {
  console.log(point.x, point.y);
}

可将Point视为一个构造器 (constructor),每次调用Point(x, y)都会创建并返回该类的一个对象,且含有x及y的属性名称。points则是一个含有多个Point对象的数组。上面的代码遍历出每个Point对象后,赋值于point变量,然后打印出它们的x值及y值。

如果我们不希望每次都通过引用对象属性的方式来访问x及y值,则可编写代码如下:

for (const point of points) {
  let x=point.x;
  let y=point.y;
  console.log(x, y);
}

这一步可利用ES6的const解包特性予以简化:

for (const point of points) {
  const {x, y}=point;
  console.log(x, y);
}

更进一步,我们可以直接解包:

for (const {x, y} of points) {
  console.log(x, y);
}

2.5 遍历Map

let scoreMap=new Map([
    ['Mike', 75],
    ['Tom', 80],
    ['Smith', 90]
]);

for (const [key, value] of scoreMap) {
    console.log(key, value);
}

与上一节不同的是,Map需要使用 [key, value] 的方式来解包。

3、forEach 方法


3.1 forEach 常见调用方式

for...in,for...of 均是语句,与它们不同的是,forEach是数组的内嵌方法。这意味着我们可以直接在数组对象上面直接调用该方法。

let arr=[1, 3, 5, 7, 9];

arr.forEach((element)=> {
  console.log(element);
});

作为数组方法,forEach有一个参数,该参数的类型是函数,称为回调函数 (callback function)。所谓回调函数,是指一旦程序员提供了这样的函数,JavaScript引擎将负责调用此函数。

回调函数的价值在于回调函数可能存在多个参数,而这些参数将由JavaScript引擎自动提供。在回调函数中,我们可对JavaScript引擎所自动提供的参数进行进一步加工。

在上面的回调函数中,element是由JavaScript引擎自动提供的,代表每个数组元素。

上面的代码采用了lambda匿名表达式。它等同于:

let arr=[1, 3, 5, 7, 9];

function callback(element) {
  console.log(element);
}

arr.forEach(callback);

可见,lambda表达式更加简练。

3.2 forEach 的参数

forEach共有3个参数 (上面例子只用了第1个),它们的排列顺序如下:

arr.forEach((element, index, array)=> {
  console.log(element);
  console.log(index);
  console.log(array);
});

参数element是数组元素,参数index是数组元素所在数组中的索引值,参数array是整个数组。

一般情况下,我们仅需用到element及index参数就足够了。由于是每次迭代,因此,forEach方法中的array参数较少用到。

index每次遍历时都会加1,且每次都会与array的长度比较。一旦超出array的界限,就会终止遍历。如果遍历过程中,修改了array的长度,则需特别注意这一点。

3.2 forEach 遍历的终止

如何中止forEach的遍历?JavaScript并未提供这样的机制,但我们可以用一个双重嵌套的异常来终止遍历。

let arr=[1, 3, 5, 7, 9];

try {
  arr.forEach((element, index, array)=> {
    try {
      console.log(index);
      if (index >=3) {
        throw new Error('forEach termination signal');
      }
    } catch (error) {
      throw error;
    }
  });
} catch (e) {
  if (e.message==='forEach termination signal') {
    console.log('forEach terminated.');
  }
}

console.log('This line of code should be executed.');

显示:

0
1
2
3
forEach terminated.
This line of code should be executed.

我们设定,当index的值大于等于3时,需要终止遍历。这样,在第7行,当此条件满足时,即抛出"forEach termination signal"的异常。

此时,程序流程转入到第10行至第12行最内层的异常捕获处理代码:

} catch (error) {
    throw error;
}

捕获异常后,如果我们不重新抛出异常,JavaScript引擎则会认为我们已正确地处理了异常,因此会恢复中断的遍历进程,继续处理下一个数组元素,这不是我们想要的。因此,我们在此重新抛出该异常,以切实终止遍历。

这时,forEach的遍历因异常而终止,从而达到了我们的最初的目标。但因为有异常,如果我们未作任何处理,则该异常会导致整个程序都终止运行。只有在我们处理了异常后,程序才能往下走。这就是第14行至18行最外层异常捕获代码的作用:

} catch (e) {
  if (e.message==='forEach termination signal') {
    console.log('forEach terminated');
  }
}

先判断它是不是"forEach termination signal"。如果是,则简单地打印一行消息。由于这里未再抛出新的异常,因此JavaScript引擎认为我们已经正确地处理了异常,则继续执行后面的代码。这样,最后一行第20行语句将被执行并打印出"This line of code should be executed."的文本。

一般来讲,如果我们需要在数组的遍历过程中终止遍历,不要使用 forEach 语句,使用最传统的方式即可:

let arr=[1, 3, 5, 7, 9];

for (let i=0; i < arr.length; i++) {
  console.log(i, arr[i]);
  if (i >=3) {
    break;
  }
}

console.log('This line of code should be executed.');

这样即可在遍历中访问数组的索引值与数组元素,又可以极为方便地随时终止遍历。