整合营销服务商

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

免费咨询热线:

数组遍历:JavaScript算法题的解题利器

JavaScript作为Web前端开发的基石,其强大的功能和灵活性不仅体现在网页的动态交互上,更在于其处理数据的能力。数组遍历是JavaScript中最常见的操作之一,尤其在算法题的求解过程中,它扮演着至关重要的角色。本文将深入探讨JavaScript中数组遍历的多种方法,通过具体的算法题示例,帮助读者掌握高效解决问题的技巧。


技术概述

数组遍历方法

在JavaScript中,数组遍历可以通过多种方式进行,每种方法都有其特点和适用场景:

  • for循环:最传统的遍历方式,适用于所有情况。
  • forEach():ES5引入的数组方法,简化了遍历语法。
  • map():用于创建新数组,对原数组的每个元素进行映射操作。
  • filter():用于筛选数组,返回满足条件的元素组成的新数组。
  • reduce():用于对数组元素进行累积操作,常用于求和、合并等场景。
  • some()every():用于检查数组中是否存在满足条件的元素或所有元素是否都满足条件。

代码示例

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

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

// 使用forEach遍历
numbers.forEach(number => console.log(number));

// 使用map创建新数组
const doubled = numbers.map(number => number * 2);
console.log(doubled); // 输出: [2, 4, 6, 8, 10]

技术细节

工作原理

数组遍历方法本质上是通过迭代数组中的每一个元素来执行特定的逻辑操作。不同的方法提供不同的操作能力,如map用于变换,filter用于筛选,而reduce用于聚合。

难点分析

  • 性能考量:尽管现代JavaScript引擎进行了大量的优化,但在处理大规模数据时,遍历方法的选择仍然会影响性能。
  • 副作用管理:在遍历时避免对原始数组造成不必要的修改,尤其是使用mapfilter时。

实战应用

应用场景

假设我们有一道算法题,要求找出数组中所有偶数,并返回它们的平方和。

代码示例

function sumOfSquaresEvenNumbers(numbers) {
    return numbers
        .filter(number => number % 2 === 0) // 筛选偶数
        .map(number => number * number)     // 平方
        .reduce((acc, curr) => acc + curr, 0); // 求和
}

const result = sumOfSquaresEvenNumbers([1, 2, 3, 4, 5, 6]);
console.log(result); // 输出: 56

优化与改进

潜在问题

  • 性能瓶颈:对于大数据集,多次迭代可能会导致性能下降。
  • 代码冗余:过度使用高阶函数可能导致代码不易理解。

代码示例

function optimizedSumOfSquaresEvenNumbers(numbers) {
    let sum = 0;
    for (let number of numbers) {
        if (number % 2 === 0) {
            sum += number * number;
        }
    }
    return sum;
}

const optimizedResult = optimizedSumOfSquaresEvenNumbers([1, 2, 3, 4, 5, 6]);
console.log(optimizedResult); // 输出: 56

常见问题

  • Q: 如何在遍历数组时避免修改原数组?
  • A: 使用mapfilter等方法,它们会返回新数组,而不会修改原数组。

总结与展望

数组遍历不仅是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.');

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


随着ES6中新增对数组的find方法,对于数组的遍历已经有越来越多的方法可以选择,我们完全可以抛弃古老的for方法。今天这篇文章我们就一起来看看在Javascript中都可以使用哪些方法来遍历一个数组吧,同时提供一些兼容性的处理。

本篇文章中的代码已经开源到Github上,感兴趣的可以自取,Github地址为:

https://github.com/zhouxiongking/article-pages/blob/master/articles/arrayTraverse/arrayTraverse.js

Javascript

forEach

forEach是数组遍历使用最为频繁的一个方法,它的作用是使用定义的函数处理数组中的每个元素

首先我们来看看forEach的基本使用方法。

forEach基本使用

forEach中定义的回调函数接收三个参数,分别是当前元素值,当前元素索引,当前数组。

实际上forEach方法接收第二个参数,如果传入这个参数,则回调函数中的this就指向这个参数值,如果没有传入,则this指向全局变量window。通过下面的代码可以很容易看清楚。

传入第二个参数

这里我们提供一个兼容性处理的方案。

forEach兼容性处理

map

map方法会将数组中每个元素做处理得到新的元素,然后返回这些新的元素组成的数组。其回调函数中接收的参数和forEach一样,其基本使用如下。

map基本使用

需要注意的一点是,在map的回调函数中需要有return返回值,如果没有返回值,得到的结果会是由undefined组成的数组。

必须有返回值

这里提供一下兼容性处理。

map兼容性处理

filter

filter顾名思义,过滤数组中满足条件的数值,得到一个新的数组。在filter的回调函数中需要返回true或者false,true代表满足条件,通过筛选;false代表不满足条件,不通过筛选。

filter基本使用方法

这里提供一下兼容性处理。

filter兼容性处理

some与every

some方法与every方法在使用上有很多相似之处。some方法的作用是只要数组中某个元素满足条件就返回true;而every方法作用是数组中每个元素都要满足条件才返回true

它们的测试结果如下所示。

some与every

some与every方法的兼容性处理也很类似,一个是对true值的判断,一个是对false值的判断。对some方法的兼容性处理如下。

some方法兼容性处理

every方法兼容性处理如下。

every兼容性处理

reduce

reduce方法作用是使用可能提供的初始值来处理数组中的每个元素,每轮计算都会有一个返回值进入下一轮计算,得到一个最终的返回值

reduce在是否接收初始值上会对结果有很大的影响。在不接收初始值的情况下,会将第一个值作为初始值,索引是从第二个值开始计算。通过下面的代码可以很容易看出来。

reduce不接收初始值

当reduce接收初始值时,索引是从数组第一个值开始。

reduce接收初始值

这里提供对reduce方法的兼容性处理。

reduce方法兼容性处理

find

find方法是ES6新增的方法,其作用是返回数组中第一个满足条件的值,如果都不满足条件则返回undefined。其基本使用方法如下所示。

find基本使用

这里提供一下兼容性处理。

find兼容性处理

结束语

今天这篇文章主要总结了下Javascript中对数组进行遍历的一些方法,以及对低级浏览器提供了一些兼容性处理,你学会了吗?