前端必会!深入剖析JS中forEach、for、map的区别
引言在 JavaScript 编程的世界里,数组操纵是极为常见的任务,而forEach、for循环以及map方法,都是我们在遍历数组时的得力工具。虽然它们都能实现对数组元素的遍历访问,但在实际利用过程中,它们却有着各自独特的特性、语法和适用场景。对于前端开发者而言,深入明白并熟练掌握它们之间的区别,就如同掌握了一把开启高效编程大门的钥匙,能够在不同的业务需求下,精准地选择最符合的遍历方式,从而编写出简便、高效且易于维护的代码,提升开发效率和代码质量。
基本语法展示
在正式探究它们的区别之前,我们先来熟悉一下这三者的基本语法布局,这是我们深入明白和运用它们的基础。
for 循环
for循环是最传统的循环布局,在各种编程语言中都广泛存在,其语法形式如下:
for (初始化表达式; 条件判断表达式; 循环后操作表达式) {
// 循环体代码
}
此中,初始化表达式在循环开始前实验一次,通常用于初始化循环变量;条件判断表达式在每次循环开始时进行判断,若结果为true,则实验循环体代码,否则终止循环;循环后操纵表达式在每次循环体代码实验完毕后实验,通常用于更新循环变量。
例如,遍历一个数组并打印每个元素:
const arr = ;
for (let i = 0; i < arr.length; i++) {
console.log(arr);
}
forEach 方法
forEach是数组的一个方法,用于对数组中的每个元素实验一次提供的回调函数,其语法如下:
array.forEach((currentValue, index, array) => {
// 回调函数代码
}, thisValue);
currentValue表示当前正在处置处罚的元素;index表示当前元素在数组中的索引(可选);array表示调用forEach方法的数组本身(可选);thisValue是可选参数,用于指定回调函数中this的值。
示例:
const numbers = ;
numbers.forEach((number, index) => {
console.log(`索引 ${index} 处的元素是 ${number}`);
});
map 方法
map同样是数组的方法,它会创建一个新数组,新数组中的元素是原数组中每个元素调用提供的回调函数后的返回值,语法如下:
const newArray = array.map((currentValue, index, array) => {
// 回调函数代码,必须有返回值
}, thisValue);
参数含义与forEach方法雷同。
例如,将数组中的每个元素翻倍并天生一个新数组:
const originalArray = ;
const doubledArray = originalArray.map((number) => {
return number * 2;
});
console.log(doubledArray);
功能差异
for 循环
for循环作为一种基础且通用的循环布局,拥有极高的灵活性,是很多开发者在进行复杂循环操纵时的首选。它的语法虽然相对复杂,但正是这种复杂性赋予了它强盛的控制本领。通过自界说初始化表达式、条件判断表达式和循环后操纵表达式,开发者可以精确地控制循环的实验次数、起始条件和竣事条件。例如,在遍历数组时,我们可以通过调解for循环的变量来实现跳跃式遍历,大概根据特定条件提前终止循环。在一些必要对数组进行复杂操纵的场景中,如矩阵运算、数据排序等,for循环能够提供最直接、最灵活的实现方式。同时,for循环不仅局限于数组遍历,还可以用于各种必要重复实验代码块的场景,如循环天生 HTML 元素、控制动画的帧数等。
forEach 方法
forEach方法是专门为数组遍历计划的,它提供了一种简便、直观的方式来处置处罚数组中的每一个元素。forEach方法会自动遍历数组的每一项,并将当前元素、索引和数组本身作为参数通报给回调函数。在回调函数中,我们可以对每个元素进行各种操纵,如打印元素、修改元素属性等。例如,在处置处罚一个包罗用户信息的数组时,我们可以利用forEach方法快速遍历数组,并将每个用户的姓名打印出来。然而,forEach方法也有其局限性,它一旦开始实验,就会一直遍历完整个数组,无法中途制止。这意味着,假如在遍历过程中碰到某个特定条件必要提前终止循环,forEach方法就无法满足需求。此外,forEach方法的返回值是undefined,这使得它不适合用于必要返回新数组或计算结果的场景。
map 方法
map方法的核心功能是遍历数组,并根据回调函数的返回值创建一个新的数组。这使得map方法在数据转换和映射场景中体现精彩。例如,在将一个包罗数字的数组中的每个元素翻倍,大概将一个包罗字符串的数组中的每个字符串转换为大写时,map方法可以轻松实现。map方法不会改变原数组,这保证了数据的不可变性,使得代码更加安全和可预测。在进行数据处置处罚时,我们可以放心地利用map方法对数据进行转换,而不消担心会影响原数据。此外,map方法返回的新数组可以方便地与其他数组方法(如filter、reduce等)链式调用,从而实现更加复杂的数据处置处罚逻辑。例如,我们可以先利用map方法对数组进行转换,然后再利用filter方法对转换后的数组进行筛选,最后利用reduce方法对筛选后的数组进行累加。
性能对比
在实际编程中,性能是我们选择利用何种遍历方式的重要考量因素之一。下面我们将通过具体的测试,来深入分析for循环、forEach方法和map方法在性能上的体现。
测试环境说明
本次测试在 Node.js 环境下进行,版本为 v16.14.2。利用console.time()和console.timeEnd()方法来测量代码的实验时间,这两个方法可以方便地记录代码块的开始和竣事时间,从而计算出代码的实验耗时。虽然这种方式并不是最精确的性能测量工具,但足以帮助我们对这三种遍历方式的性能差异有一个大抵的了解。同时,为了淘汰测试误差,每个测试用例都实验多次,取平均值作为最终的测试结果。
测试用例计划
我们创建一个包罗 100000 个元素的数组,然后分别利用for循环、forEach方法和map方法对数组进行遍历,并在遍历过程中实验一个简朴的操纵,比如将每个元素乘以 2。测试代码如下:
// 创建测试数组
const largeArray = Array.from({ length: 100000 }, (_, i) => i);
// for循环测试
console.time('for loop');
for (let i = 0; i < largeArray.length; i++) {
largeArray *= 2;
}
console.timeEnd('for loop');
// forEach方法测试
console.time('forEach');
largeArray.forEach((value, index) => {
largeArray = value * 2;
});
console.timeEnd('forEach');
// map方法测试
console.time('map');
const newArray = largeArray.map((value) => {
return value * 2;
});
console.timeEnd('map');
性能测试结果分析
经过多次测试,我们得到的平均结果如下:
[*]for循环:平均实验时间约为 毫秒。
[*]forEach方法:平均实验时间约为 毫秒。
[*]map方法:平均实验时间约为 毫秒。
从测试结果可以显着看出,for循环的性能通常是最好的,forEach方法次之,map方法的性能相对较差。这是因为:
[*]for循环是最基础的循环布局,它没有额外的函数调用开销和上下文切换,直接通过索引访问数组元素,实验过程简朴直接,因此性能最高。
[*]forEach方法作为数组的原型方法,本质上是一个高阶函数,它在每次调用回调函数时,必要创建新的函数作用域,处置处罚参数通报和上下文绑定等操纵,这些额外的操纵会带来一定的性能开销。
[*]map方法不仅具有和forEach雷同的函数调用开销,还必要创建一个新的数组来存储回调函数的返回值,这涉及到内存的分配和初始化,进一步增加了性能开销,所以在性能上体现最差。
然而,性能并不是选择遍历方式的唯一标准,在实际开发中,我们还必要综合考虑代码的可读性、可维护性以及具体的业务需求,来选择最符合的遍历方式。
利用场景分析
必要灵活控制循环时
当我们在处置处罚数组时,假如必要根据特定条件提前终止循环,大概跳过某些元素继承下一次循环,for循环就展现出了它无可替代的优势。例如,在一个包罗弟子结果的数组中,我们要查找第一个及格(结果大于即是 60 分)的弟子,一旦找到就制止查找。这种情况下,利用for循环共同break语句就能轻松实现:
const scores = ;
for (let i = 0; i < scores.length; i++) {
if (scores >= 60) {
console.log(`第 ${i + 1} 个学生及格了,成绩是 ${scores}`);
break;
}
}
又大概,我们要遍历数组,但跳过某些特定索引的元素,利用for循环联合continue语句也能很好地完成任务:
const numbers = ;
for (let i = 0; i < numbers.length; i++) {
if (i % 3 === 0) {
continue;
}
console.log(numbers);
}
在这些场景中,forEach和map方法由于无法直接利用break和continue语句,就难以实现这样灵活的控制。
仅进行数组遍历和操纵时
假如我们的需求仅仅是遍历数组,并对每个元素实验一些操纵,比如打印元素、修改元素本身等,而不必要返回新数组,forEach方法就能派上用场。它的语法简便明白,让代码看起来更加简便和直观。例如,在一个包罗商品信息的数组中,我们要为每个商品添加一个默认的库存数量:
const products = [
{ name: '商品A' },
{ name: '商品B' },
{ name: '商品C' }
];
products.forEach((product) => {
product.stock = 100;
});
console.log(products);
这种情况下,利用forEach方法,代码逻辑清晰,易于明白和维护。而且forEach方法不必要手动管理索引,淘汰了出错的可能性。
必要天生新数组时
当我们必要根据原数组天生一个新数组,并且新数组中的元素与原数组元素存在某种映射关系时,map方法无疑是最佳选择。例如,在一个包罗数字的数组中,我们要天生一个新数组,新数组中的每个元素是原数组对应元素的平方:
const originalNumbers = ;
const squaredNumbers = originalNumbers.map((number) => {
return number * number;
});
console.log(squaredNumbers);
利用map方法,我们可以简便高效地完成数组的映射操纵,天生符合需求的新数组。并且map方法不会改变原数组,这在很多必要保持数据原始状态的场景中非常重要。
注意事项和常见错误
forEach 中 return 无效
在利用forEach方法时,必要特殊注意的是,return语句并不会像在平凡函数中那样返回值或终止循环。这是因为forEach方法会强制遍历完整个数组,无论回调函数中是否实验了return语句。例如,当我们想要在forEach遍历数组时,一旦找到某个特定元素就制止遍历并返回结果,像下面这样写是无法实现的:
const numbers = ;
let result;
numbers.forEach((number, index) => {
if (number === 3) {
result = number;
return; // 这里的return不会终止forEach循环
}
});
console.log(result);
在这个例子中,即使找到了值为 3 的元素并实验了return语句,forEach方法仍然会继承遍历数组的剩余元素。假如想要实现雷同的功能,我们可以利用for循环共同break语句,大概利用find方法等。
map 方法对原数组的影响
虽然map方法不会直接修改原数组,而是返回一个新数组,但在实际利用中,轻易因为对引用类型数据的操纵而产生误解。当原数组中的元素是引用类型(如对象或数组)时,假如在map的回调函数中直接修改了元素的属性,那么原数组中的元素也会随之改变。例如:
const students = [
{ name: '小明', score: 80 },
{ name: '小红', score: 90 }
];
const newStudents = students.map((student) => {
student.score += 10; // 直接修改对象属性
return student;
});
console.log(students);
console.log(newStudents);
在这个例子中,newStudents和students中的对象实际上是同一个对象,因为我们在map回调函数中直接修改了原对象的属性。为了避免这种情况,在利用map方法时,应该尽量保持数据的不可变性,避免直接修改原数组中的对象,而是创建新的对象来存储修改后的值,例如:
const students = [
{ name: '小明', score: 80 },
{ name: '小红', score: 90 }
];
const newStudents = students.map((student) => {
return {...student, score: student.score + 10 }; // 创建新对象
});
console.log(students);
console.log(newStudents);
循环中的作用域问题
在利用for循环、forEach方法和map方法时,还必要注意变量的作用域问题。在for循环中,假如利用var声明循环变量,由于var具有函数作用域,可能会导致意外的变量访问和修改。例如:
function test() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr.push(function () {
console.log(i); // 这里的i在循环结束后的值是5
});
}
return arr;
}
const functions = test();
functions.forEach((func) => {
func();
});
在这个例子中,由于i是用var声明的,具有函数作用域,所以在循环竣事后,i的值为 5,当我们调用arr中的函数时,打印出来的都是 5。为了避免这种问题,在 ES6 中,我们可以利用let或const来声明循环变量,它们具有块级作用域,每次迭代都会创建一个新的变量副本,例如:
function test() {
var arr = [];
for (let i = 0; i < 5; i++) {
arr.push(function () {
console.log(i); // 这里会正确打印出每次迭代时i的值
});
}
return arr;
}
const functions = test();
functions.forEach((func) => {
func();
});
在forEach和map方法中,虽然回调函数有自己的作用域,但也要注意不要在回调函数中意外地访问和修改外部作用域中的变量,以免造成难以调试的错误。
总结
通过对for循环、forEach方法和map方法的深入剖析,我们清晰地了解到它们在语法、功能、性能以及利用场景上都存在着显著的区别。for循环作为最基础的循环布局,赋予了开发者高度的灵活性,能够在各种复杂的循环控制场景中发挥关键作用;forEach方法以其简便直观的语法,成为简朴数组遍历和操纵的首选工具;map方法则凭借其强盛的数据映射本领,在天生新数组的场景中体现杰出。在实际的前端开发工作中,我们不应盲目地选择某种遍历方式,而是要根据具体的业务需求、代码的可读性和可维护性,以及性能要求等多方面因素,综合权衡并选择最符合的遍历方式。只有这样,我们才能编写出更加高效、优质的代码,提升开发效率,为用户带来更好的体验。希望通过本文的介绍,能帮助各人在面临数组遍历问题时,做出更加明智的选择 。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]