本文深入解析JavaScript中forEach与sort方法的行为差异。重点讲解forEach在遍历基本类型与引用类型时对原数组的影响,以及sort方法默认按Unicode排序导致的数字排序异常,并提供自定义比较函数及箭头函数的标准写法。
forEach() 方法概述
该方法接收一个回调函数作为参数。此函数由开发者定义,但由引擎自动调用。
回调函数接收三个参数:
- 第一个参数代表当前遍历的元素。
- 第二个参数代表当前元素的索引。
- 第三个参数代表正在遍历的原始数组。
代码示例如下:
let myArr = ['王一', '王二', '王三'];
myArr.forEach((item, index, arr) => {
console.log('item:' + item);
console.log('index:' + index);
console.log('arr:' + JSON.stringify(arr));
});输出结果如下:
item:王一 index:0 arr:["王一","王二","王三"] ---------- item:王二 index:1 arr:["王一","王二","王三"] ---------- item:王三 index:2 arr:["王一","王二","王三"] ----------
注意:forEach() 方法没有返回值,其返回值始终为 undefined。
因此,尝试通过 let tempArry = myArr.forEach() 这种赋值方式接收结果是没有意义的。
forEach() 能否修改原数组?
关于此问题,许多开发者存在误解。我们通过以下代码进行分析。
1、数组元素为基本数据类型:(无法直接修改原数组)
let numArr = [1, 2, 3];
numArr.forEach((item) => {
item = item * 2;
});
console.log(numArr); // 打印结果:[1, 2, 3]请注意上述代码的执行结果,输出为 [1, 2, 3],而非 [2, 4, 6]。
2、数组元素为引用数据类型:(直接替换整个元素对象时,原数组不变)
let objArr = [
{ name: '云牧', age: 20 },
{ name: '许嵩', age: 30 },
];
objArr.forEach((item) => {
item = {
name: '邓紫棋',
age: '29',
};
});
console.log(JSON.stringify(objArr));
// 打印结果:[{"name": "云牧","age": 20},{"name": "许嵩","age": 30}]3、数组元素为引用数据类型:(修改元素对象的属性时,原数组会改变)
let objArr = [
{ name: '云牧', age: 28 },
{ name: '许嵩', age: 30 },
];
objArr.forEach((item) => {
item.name = '邓紫棋';
});
console.log(JSON.stringify(objArr));
// 打印结果:[{"name":"邓紫棋","age":28},{"name":"邓紫棋","age":30}]由此可见,forEach 本身不会直接修改调用它的数组对象。但如果回调函数修改了引用类型对象的内部属性,原数组会随之变化。理解基本数据类型的值传递与引用数据类型的地址传递机制即可明白这一现象。
B站已发布关于基础数据类型与引用数据类型区别的视频,若对此概念不清,请务必补充相关知识。
若需通过 forEach 修改原数组,建议利用参数 2(索引)和参数 3(数组本身)进行操作,具体标准做法见下文。
通过参数 2、参数 3 修改原数组的标准做法:
// 1、数组的元素是基本数据类型
let numArr = [1, 2, 3];
numArr.forEach((item, index, arr) => {
arr[index] = arr[index] * 2;
});
console.log(JSON.stringify(numArr)); // 打印结果:[2, 4, 6]
// 2、数组的元素是引用数据类型时,直接修改对象
let objArr = [
{ name: '云牧', age: 28 },
{ name: '许嵩', age: 34 },
];
objArr.forEach((item, index, arr) => {
arr[index] = {
name: '小明',
age: '10',
};
});
console.log(JSON.stringify(objArr));
// 打印结果:[{"name":"小明","age":"10"},{"name":"小明","age":"10"}]
// 3、数组的元素是引用数据类型时,修改对象的某个属性
let objArr2 = [
{ name: '云牧', age: 28 },
{ name: '许嵩', age: 34 },
];
objArr2.forEach((item, index, arr) => {
arr[index].name = '小明';
});
console.log(JSON.stringify(objArr2));
// 打印结果:[{"name":"小明","age":28},{"name":"小明","age":34}]总结:
若仅需遍历数组,使用 forEach() 即可。
但若希望在遍历过程中改变元素内容,建议使用 map() 方法。map() 会返回一个新数组,避免使用 forEach() 以防止出现意外错误。
sort() 方法概述
sort():对数组元素进行从小到大排序(会直接修改原数组)。
若在使用 sort() 时不传参,
默认排序顺序是将元素转换为字符串后,按照 Unicode 编码值从小到大进行排序。
示例 1:(数组元素为字符串时)
let arr1 = ['e', 'b', 'd', 'a', 'f', 'c'];
let result = arr1.sort(); // 将数组 arr1 进行排序
console.log('arr1 =' + JSON.stringify(arr1));
console.log('result =' + JSON.stringify(result));输出结果:
arr1 =["a","b","c","d","e","f"] result =["a","b","c","d","e","f"]
从上述输出可见,sort 方法会直接修改原数组,且返回值与排序后的数组一致。
示例 2:(数组元素为数字时)
let arr2 = [5, 2, 11, 3, 4, 1];
let result = arr2.sort(); // 将数组 arr2 进行排序
console.log('arr2 =' + JSON.stringify(arr2));
console.log('result =' + JSON.stringify(result));输出结果:
arr2 = [1,11,2,3,4,5] result = [1,11,2,3,4,5]
观察上述结果,数字 11 排在数字 2 之前。原因在于 sort() 方法默认按 Unicode 编码排序,导致数字被转为字符串比较。
若希望 arr2 中的数字严格按数值从小到大排序,请继续往下看。
sort() 方法:带参时的自定义排序规则
若在 sort() 方法中传入参数,即可自定义排序规则。具体做法如下:
在 sort() 中添加一个回调函数以指定排序逻辑。
回调函数需定义两个形参,浏览器将使用数组中的元素作为实参调用该函数。
浏览器根据回调函数的返回值决定元素顺序:(重要)
- 若
compareFunction(a, b)小于 0,则 a 排在 b 之前;- 若
compareFunction(a, b)等于 0,a 和 b 的相对位置保持不变;- 若
compareFunction(a, b)大于 0,则 b 排在 a 之前。仅看文字可能难以理解,请看以下示例,即可明白。
let arr = [5, 2, 11, 3, 4, 1];
arr.sort(function (a, b) {
console.log("a:" + a, "b:" + b);
});
/*
a:2 b:5
a:11 b:2
a:3 b:11
a:4 b:3
a:1 b:4
*/示例:将数组中的数字按从小到大排序
写法 1:
let arr = [5, 2, 11, 3, 4, 1];
// 自定义排序规则
let result = arr.sort(function (a, b) {
if (a > b) {
// 如果 a 大于 b,则 b 排列 a 之前
return 1;
} else if (a < b) {
// 如果 a 小于 b,,则 a 排列 b 之前
return -1;
} else {
// 如果 a 等于 b,则位置不变
return 0;
}
});
console.log('arr =' + JSON.stringify(arr));
console.log('result =' + JSON.stringify(result));输出结果:
arr = [1, 2, 3, 4, 5, 11]; result = [1, 2, 3, 4, 5, 11];
上述代码写法较为繁琐,可简化为如下形式:
写法 2:
let arr = [5, 2, 11, 3, 4, 1];
// 自定义排序规则
let result = arr.sort(function (a, b) {
return a - b; // 升序排列
// return b - a; // 降序排列
});
console.log('arr =' + JSON.stringify(arr));
console.log('result =' + JSON.stringify(result));输出结果不变。
上述代码还可改写为 ES6 语法,即将 function 替换为箭头函数,写法如下:
写法 3:(箭头函数)
let arr = [5, 2, 11, 3, 4, 1];
// 自定义排序规则
let result = arr.sort((a, b) => {
return a - b; // 升序排列
});
console.log('arr =' + JSON.stringify(arr));
console.log('result =' + JSON.stringify(result));由于函数体仅有一行语句,可省略 return 语句,进一步简化为:
写法 4:(推荐)
let arr = [5, 2, 11, 3, 4, 1];
// 自定义排序规则:升序排列
let result = arr.sort((a, b) => a - b);
console.log('arr =' + JSON.stringify(arr));
console.log('result =' + JSON.stringify(result));在上述各种写法中,写法 4 是实战开发中使用频率最高的。
为确保代码简洁优雅,后续涉及函数的代码将尽量采用 ES6 箭头函数书写。
sort 方法举例:按对象字段排序
数字排序是常见场景,但实际开发中常涉及更复杂的需求。
以下代码在实际开发中频繁使用,务必掌握。完整代码如下:
let dataList = [
{
title: "品牌鞋子,高品质低价入手",
publishTime: 200,
},
{
title: "不是很贵,但是很暖",
publishTime: 100,
},
{
title: "无法拒绝的美食,跟我一起吃吃",
publishTime: 300,
},
];
console.log("排序前的数组:" + JSON.stringify(dataList));
// 将dataList 数组,按照 publishTime 字段,从小到大排序。(会改变原数组)
dataList.sort((a, b) => parseInt(a.publishTime) - parseInt(b.publishTime));
console.log("排序后的数组:" + JSON.stringify(dataList));输出结果:
排序前的数组:[ {"title":"品牌鞋子,高品质低价入手","publishTime":200}, {"title":"不是很贵,但是很暖","publishTime":100}, {"title":"无法拒绝的美食,跟我一起吃吃","publishTime":300} ]
排序后的数组:[ {"title":"不是很贵,但是很暖","publishTime":100}, {"title":"品牌鞋子,高品质低价入手","publishTime":200}, {"title":"无法拒绝的美食,跟我一起吃吃","publishTime":300} ]
有人可能会问:publishTime 字段已是 number 类型,为何排序前还要执行 parseInt() 转换?
这是因为此类数据通常由后台接口返回,类型可能是 number 或 string。统一先执行 parseInt() 转换数字是更稳妥的做法,这属于良好的编码习惯。
此外,定义变量时建议优先使用 const 以保证程序安全,仅在需要重新赋值时使用 let。
最后补充:forEach、map、filter、some、every、find、findIndex 等数组原型方法除接收第一个回调函数外,还可接收第二个参数以指定回调函数内的 this 值(较少使用)。需注意,若回调函数使用箭头函数,则无法绑定 this,因为箭头函数没有独立的 this,始终继承上层作用域的 this(面试常考点)。此外,箭头函数也没有 arguments 对象,但可使用剩余参数 ... 解决。
总结
至此,关于 JavaScript 中 forEach 修改原数组与 sort 排序的经典场景介绍完毕。如需了解更多 JavaScript forEach 修改原数组相关内容,请搜索本站历史文章或继续浏览相关文章。感谢大家的支持!