数组循环遍历方法总结及区别


前言

不同的场景使用不同的遍历方法可以大大提高计算效率,优化代码结构。在总结循环的之前,先知道一下循环结构的执行步骤:

  1. 声明循环变量;
  2. 判断循环条件;
  3. 执行循环体操作;
  4. 更新循环变量;
  5. 然后循环执行 2-4,直到条件不成立,跳出循环。

一、for 循环

	const arr = [3, 8, 99, 44, 3]
	for(let i = 0; i < arr.length; i++) {
		console.log(arr[i])
	}
	// 当循环中数组的长度没有变化时,我们应该将数组的长度存储在一个变量中,这样效率会更高。下面是改进的写法:
	for(let i = 0, len = arr.length; i < len; i++ ) {
		console.log(arr[i])
	}

二、while 循环

	let num = 1 // 1、声明循环变量       
	while (num <= 10) { // 2、判断循环条件
	    console.log(num) // 3、执行循环体操作
	    num++ // 4、更新循环变量
	}

三、do…while… 循环

  • while循环特点:先判断后执行;
  • do-while循环特点:先执行再判断,即使初始条件不成立,do-while循环至少执行一次;
	let num = 10    
	do {
	    console.log(num) // 10 9 8 7 6 5 4 3 2 1 0
	    num--
	  } while(num >= 0)            
	 console.log(num) // -1

四、forEach

  • 在 ES5 中,引入了一个新循环,即 forEach 循环。
	const arr = [3, 8, 99, 44, 3]
	arr.forEach((value) => {
		console.log(value)
	})
	// 操作的结果如下:3, 8, 99, 44, 3
  • forEach 方法对数组中包含有效值的每一项执行一次回调函数,那些已经被删除(使用delete 方法等)或从未赋值的项将被跳过(不包括那些未定义的项) 或空值)。回调函数会依次传入三个参数:
  1. 数组中当前项的值;
  2. 当前项在数组中的索引;
  3. 数组对象本身;
  • 需要注意的是,forEach 遍历的范围会在第一次调用回调之前确定。调用 forEach 后添加到数组的项目不会被回调访问。
  • 如果现有值发生变化,则传递给callback的值就是forEach遍历它们时的值。不会遍历已删除的项目。
	const arr = [];
	arr[0] = 'a'
	arr[3] = 'b'
	arr[10] = 'c'
	arr.name = 'Hello world'
	arr.forEach((value, index, array) => {
		console.log(value, index, array)
	})
	// 操作结果:
	'a', 0, ['a', 3: 'b', 10: 'c', name: 'Hello world']
	'b' 3, ['a', 3: 'b', 10: 'c', name: 'Hello world']
	'c' 10, ['a', 3: 'b', 10: 'c', name: 'Hello world']
  • 这里的索引是Number类型的,不会像for-in那样遍历原型链上的属性。
  • 因此,在使用forEach时,我们不需要特别声明索引和要遍历的元素,因为这些都是作为回调函数的参数。
  • forEach 性能
    您可以看看jsPerf。在不同浏览器下测试的结果是forEach没有for快。如果将测试代码放在控制台中,可能会得到不同的结果。主要原因是控制台的执行环境与真实的代码执行环境不同。

五、filter

  • 用于过滤数组成员,满足条件的成员组成一个新数组返回。它的参数是一个函数,所有数组成员依次执行该函数,返回结果为true的成员组成一个新数组返回。该方法不会改变原数组。
	[1, 2, 3, 4, 5].filter(function (item, index, arr) {
	    return (item > 3)
	}) // [4, 5]
	let array = [0, 1, 'a', false]
  	array.filter(Boolean) // [1, "a"]
  	// filter方法也可以接受第二个参数,用来绑定参数函数内部的this变量。
	let obj = { MAX: 3 }
	let myFilter = function (item) {
	  if (item > this.MAX) return true
	}
	let arr2 = [2, 8, 3, 4, 1, 3, 2, 9];
	arr2.filter(myFilter, obj) // [8, 4, 9]

六、some

  • 该方法返回一个布尔值,表示判断数组成员是否符合某种条件。
  • 它接受一个函数作为参数,所有数组成员依次执行该函数。该函数接受三个参数:当前成员、位置和该数组,然后返回一个布尔值。
  • 只要一个成员的返回值是true,则整个some方法的返回值就是true,否则返回false
	[1, 2, 3, 4, 5].some(function (item, index, arr) {
	    return (item > 3)
	}) // true

七、every

  • 该方法返回一个布尔值,表示判断数组成员是否符合某种条件。
  • 它接受一个函数作为参数,所有数组成员依次执行该函数。该函数接受三个参数:当前成员、位置和该数组,然后返回一个布尔值。
  • 所有成员的返回值都是true,整个every方法才返回true,否则返回false。
	[1, 2, 3, 4, 5].every(function (elem, index, arr) {
      return elem >= 3;
    }) // false

八、reduce,reduceRight

  • reduce方法和reduceRight方法依次处理数组的每个成员,最终累计为一个值。它们的差别是,reduce是从左到右处理(从第一个成员到最后一个成员
  • reduceRight则是从右到左(从最后一个成员到第一个成员),其他完全一样。
	[1, 2, 3, 4].reduce(function (a, b, c, d) { // a:累计变量, b:当前变量 c, d为可选参数,分别表示当前位置和原数组
      console.log(a, b); // 分别为:1,2 | 3,3 | 6,4
      return a + b;
    }, total) // total 为可选参数,表示初始累计值,最后结果为total + 数组累计值

九、map

  • 无法跳出循环,返回一个新数组,第一个参数为函数,第二个参数可选,用来绑定回调函数内部的this变量,将回调函数内部的this对象,指向第二个参数。
	let arr = ['a', 'b', 'c'];
    [1, 2].map(function (item, index, arr) {
    	return this[item];
    }, arr) // 返回['b', 'c']

十、for…in… 循环

1. 数字数组

	const arr = [3, 8, 99]
	let index
	for(index in arr) {
		console.log("arr[" + index + "] = " + arr[index])
	}
	// 一般来说,操作的结果如下:
	arr[0] = 3 
	arr[1] = 8 
	arr[2] = 99
	
	// **但这样做往往会产生问题**

2. 遍历对象

	// for-in 循环遍历对象的属性,而不是数组的索引。所以for-in遍历的对象不限于数组,也可以遍历对象。示例如下:
	const person = {
		name: "zwt",
		age: 18
		gender: "男"
	}
	let info
	for(info in person) {
		console.log("person[" + info + "] = " + person[info])
	}
	// 结果如下:
	person[name] = "zwt"
	person[age] = 99
	person[gender] = "男"

	// 需要注意的是for-in遍历属性的顺序是不确定的,即输出结果的顺序与对象中属性的顺序无关,也与属性的字母顺序无关,也没有任何其他顺序。

3. 数组的真相

  • 数组是Javascript中的一个对象,Array的索引是属性名。事实上,Javascript 中的“数组”有点误导。Javascript
    中的数组与大多数其他语言中的数组不同。首先,Javascript 中的数组在内存中不是连续的。
  • 其次,Array 的索引不是指偏移量。其实Array的索引不是Number类型,而是String类型。之所以能正确使用 arr[0] 之类的写法,是因为语言可以自动改变 Number 类型。0 转换为 String 类型的“0”。
  • 因此,Javascript 中从来没有 Array 索引,只有“0”、“1”等属性。
  • 有趣的是,每个 Array 对象都有一个 length 属性,这使得它的行为更像其他语言中的数组。
  • 但是为什么遍历Array对象的时候不输出length属性呢?那是因为for-in只能遍历“可枚举属性”,length是不可枚举属性,实际上Array对象还有很多其他不可枚举属性。
	// 现在,让我们回过头来看看使用 for-in 循环数组的例子。我们修改前面遍历数组的例子:
	const arr = [3, 8, 99]
	arr.name = "Hello world"
	let index
	for(index in arr) {
		console.log("arr[" + index + "] = " + arr[index])
	}
	// 一般来说,操作的结果如下:
	arr[0] = 3 
	arr[1] = 8 
	arr[2] = 99
	arr['name'] = "Hello world"
  • 我们看到 for-in 遍历我们新的“name”属性,因为 for-in 遍历对象的所有属性,而不仅仅是“索引”。
  • 同时,需要注意的是,这里输出的索引值,即“0”、“1”、“2”不是Number类型,而是String类型,因为它们是作为属性输出的,不是索引,这是否意味着我们只能输出数组的内容,而不能向我们的 Array 对象添加新属性?答案是否定的。
  • 因为for-in不仅遍历数组本身的属性,还会遍历数组原型链上的所有可枚举属性。让我们看一个例子:
	Array.prototype.fatherName = "Father"
	const arr = [3, 8, 99]
	arr.name = "Hello world"
	let index
	for(index in arr) {
		console.log("arr[" + index + "] = " + arr[index])
	}
	// 一般来说,操作的结果如下:
	arr[0] = 3 
	arr[1] = 8 
	arr[2] = 99
	arr['name'] = "Hello world"
	arr['fatherName'] = "Father"
  • 至此,我们可以发现for-in并不适合遍历Array中的元素,它更适合遍历对象的属性,这也是它创建的初衷。有一个例外,那就是稀疏数组,阅读以下示例:
	let key;
	const arr = [];
	arr[0] = "a"
	arr[100] = "b"
	arr[10000] = "c"
	for(key in arr) {
		if(arr.hasOwnProperty(key) && /$|^[1–9]\d*$/.test(key) && key <= 4294967294) {
			console.log(arr[key])
		}
	}
  • for-in 仅遍历现有实体。上例中for-in遍历了3次(分别遍历属性为“0”、“100”、“10000”的元素,普通for循环会遍历10001次)。因此,只要处理得当,for-in 也可以在遍历 Array 中的元素方面发挥巨大的作用。
  • 为了避免重复工作,我们可以包装上面的代码:
	function arrayHasOwnIndex(array, prop) {
		return array.hasOwnProperty(prop) && /$|^[1–9]\d*$/.test(prop) && prop <= 4294967294 // 2-2
	}
	// 用法示例如下:
	for (let key in arr) {
		if (arrayHasOwnIndex(arr, key)) {
			console.log(arr[key])
		}
	}

3. for…in…性能

  • 如上所述,每次迭代操作都会同时搜索实例或原型属性。for-in 循环的每次迭代都会产生更多的开销,所以它比其他循环类型慢,一般速度是其他循环类型的 1/7。
  • 因此,除非您明确需要迭代具有未知数量属性的对象,否则您应该避免使用 for-in 循环。如果需要遍历有限数量的已知属性列表,使用其他循环会更快,例如以下示例:
	const obj = {
		prop1: 'value1',
		prop2: 'value2'
	}
	const props = ['prop1', 'prop2'];
	for(let i = 0; i <props.length; i++) {
		console.log(obj[props[i]])
	}
  • 在上面的代码中,对象的属性存储在一个数组中。与for-in搜索每个属性相比,代码只关注给定的属性,节省了循环的开销和时间。

十一、for…of… 循环

  • 我们先来看一个例子:
	const arr = ['a', 'b', 'c'];
	for(let value of arr) {
		console.log(value)
	}
	// 运行的结果是:a, b, c
  • 为什么要引入for…of… ?
  • 要回答这个问题,我们先来看看 ES6 之前的 3 种 for 循环的缺陷:
  1. forEach 不能中断和返回;
  2. for-in 的劣势更加明显。它不仅遍历数组中的元素,还遍历自定义属性,甚至访问原型链上的属性。
  3. 此外,遍历数组元素的顺序可以是随机的。
  • 所以,针对以上缺点,我们需要对原来的for循环进行改进。但是 ES6 不会破坏您已经编写的 JS 代码。
  • 目前,数以千计的网站依赖于 for-in 循环,其中一些甚至将其用于数组遍历。通过修复 for-in 循环来添加数组遍历支持会使这一切变得更加混乱,因此标准委员会在 ES6 中添加了一个新的循环语法来解决当前的问题 for-of 。
  • 那么 for-of 能做什么呢?
  1. 与forEach相比,它可以正确响应break、continue、return。
  2. for-of 循环不仅支持数组,还支持大多数类似数组的对象,例如 DOM 节点列表对象。
  3. for-of 循环还支持字符串遍历,它将字符串作为 Unicode 字符序列进行迭代。
  4. for-of 还支持 Map 和 Set(都是 ES6 中的新功能)对象遍历。
  • 总结一下,for-of 循环具有以下特点:
  1. 这是迭代数组元素的最简洁直接的语法。
  2. 这种方法避免了 for-in 循环的所有陷阱。
  3. 与 forEach 不同,它正确响应 break、continue 和 return 语句。
  4. 它不仅可以遍历数组,还可以遍历类数组对象和其他可迭代对象。
  • 然而,应该注意的是,for-of 循环不支持普通对象,但是如果您想遍历一个对象的属性,您可以使用 for-in 循环(它就是这样做的)。

总结

以上就是今天要讲的内容,本文仅仅简单介绍了循环在不同场景下的使用,这样能使我们快速便捷地处理数据的函数和方法。

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值