JS 数组常见处理方法
查找
查找数组某个元素的值
- `find()` 方法返回数组中满足提供的测试函数的第一个元素的值,找不到则返回 [`undefined`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/undefined)。
- `findLast()` 与 `find()` 用法相同,迭代顺序相反。*兼容性:(node v18+)*
```JavaScript
const arr = [ { name: "孙悟空", kind: "monkey", age: 600, }, { name: "猪八戒", kind: "Pig", age: 1200, }, { name: "沙和尚", kind: "unknow", age: 1800, }, { name: "六耳猕猴", kind: "monkey", age: 100, },];
// 简单使用
const monkey = arr.find((f) => f.name === "孙悟空");
console.log(monkey);
// { name: '孙悟空', kind: 'monkey', age: 600 }
/**
* findLast 从后往前遍历
*/
const monkey = arr.findLast((f) => f.kind === "monkey");
console.log(monkey);
// { name: '六耳猕猴', kind: 'monkey', age: 100 }
```
查找数组中某个元素的索引
- `findIndex()` 方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回 `-1` 。
- `findLastIndex()` 与 `findIndex()` 用法相同,迭代顺序相反。*兼容性:(node v18+)*
```JavaScript
// 简单使用
const firstIndex = arr.findIndex((f) => f.kind === "monkey");
console.log(firstIndex);
// 0
/**
* findLastIndex 从后往前遍历
*/
const findLastIndex = arr.findLastIndex((f) => f.kind === "monkey");
console.log(findLastIndex);
// 3
```
- `indexOf()` 方法返回数组中第一次出现给定元素的下标,如果不存在则返回 -1。
- `lastIndexOf()` 与 `indexOf()` 用法相同,迭代顺序相反。
```JavaScript
// 简单使用
const numArr = [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1];
const element = 5;
const index = numArr.indexOf(element);
const lastIndex = numArr.lastIndexOf(element);
console.log("index: ", index, "\nlastIndex", lastIndex);
// index: 4
// lastIndex 6
/**
* indexOf / lastIndexOf
* @param searchElement 第一个参数为 要查找的 元素
* @param fromIndex 第二个参数为 起始位置
*/
const _index = numArr.indexOf(element, 6);
const _lastIndex = numArr.lastIndexOf(element, 5);
console.log("_index: ", _index, "\n_lastIndex", _lastIndex);
```
查找数组中是否存在某个元素
- `includes()` 方法用来判断一个数组是否包含一个指定的值,如果包含返回 `true`,否则返回 `false`。
```JavaScript
/**
* includes
* @param searchElement 第一个参数为 要查找的 元素
* @param fromIndex 第二个参数为 起始位置
*/
const dynasty = ['唐', '宋', '元', '明', '清'];
console.log(dynasty.includes('汉'));
// false
console.log(dynasty.includes("唐"));
// true
console.log(dynasty.includes("唐", 3));
// false
```
根据索引返回索引位置元素
- `at` 方法可以通过索引返回索引位置的元素的值。一开始接触的时候内心是:WTF?我为什么不直接用中括号下标,为啥还要搞这个方法。**重点来了**,这个方法支持从尾部访问,也就是说当下标为负整数,会倒着数。*兼容性:(node v16.6+)*
```JavaScript
const nums = [1, 2, 3, 4];
console.log(nums.at(2));
// 3
// at 方法支持尾部访问,而中括号下标的方式不支持,返回了 undefined
console.log(nums.at(-3), nums[-3]);
// 2 undefined
// 当访问超出数组长度的索引时,结论一致,都是 undefined
console.log(nums.at(4), nums[4]);
// undefined undefined
```
添加
-
unshift()方法将指定元素(一个或多个)添加到数组的开头,并返回数组的新长度。此方法改变原数组const nums = [4, 5, 6]; const new_length = nums.unshift(1, 2, 3); console.log("new_length: ", new_length, "nums: ", nums); // new_length: 6 // nums: [ 1, 2, 3, 4, 5, 6 ] -
push()方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。此方法改变原数组const months = ["March", "Jan", "Feb", "Dec"]; const monthsLength = months.push("Nov"); console.log("months", months, "\nlength", monthsLength); // months [ 'March', 'Jan', 'Feb', 'Dec', 'Nov' ] // length 5 -
splice()方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法改变原数组- 第一个参数:新元素要添加到的索引位置,如果索引值超出了数组长度,会默认添加到数据末尾
- 第二个参数:删除元素个数,从第一个参数标记的位置开始删除
- 第三个参数:要添加到数组中的元素,多个元素之间用逗号隔开
用于添加时,第二个参数为 0,表示不删除原数组元素
const nations = ["Russia", "France", "America", "Britain"]; nations.splice(1, 0, "China"); console.log(nations); // ['Russia', 'China', 'France', 'America', 'Britain'] nations.splice(8, 0, "Japan", "Korea"); console.log(nations); // ['Russia', 'China', 'France', 'America', 'Britain', 'Japan', 'Korea'] -
toSpliced()方法。同样的,是splice()方法的孪生,如果期望不改变原数组,就用这个。 兼容性:(node v20+)
删除
-
splice()方法用作删除时,只用前两个参数nations.splice(2, 1); // 表示从下标为 2 的位置,删掉一个元素 console.log(nations); // [ 'Russia', 'France', 'Britain' ] -
shift()方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度const nums = [1, 2, 3]; const new_length = nums.shift(); console.log("new_length: ", new_length, "nums: ", nums); // new_length: 1 nums: [ 2, 3 ] // 如果不希望原数组发生改变,可以使用 slice 方法 const new_nums = nums.slice(1); // nums.slice(1) 删除索引 1 之前的元素,不包括索引 1 处的元素,并返回剩余元素组成的数组 console.log("new_nums: ", new_nums, "nums: ", nums); // new_nums: [ 2, 3 ] nums: [ 1, 2, 3 ] -
pop()方法从一个数组中删除并返回最后一个元素。此方法更改数组的长度1. const vegetables = ['potato', 'onion', 'cucumber', 'tomato', 'Chinese yam']; 2. const vegetable = vegetables.pop(); 3. console.log(vegetable); // Chinese yam 4. console.log(vegetables); // ['potato', 'onion', 'cucumber', 'tomato']
替换
-
with方法可以更新数组中某个索引的值,返回替换后的新数组。兼容性:(node v20+)const nums = [1, 2, 3, 4]; const newNums = nums.with(2, 10); console.log(newNums) // [1, 2, 10, 4]
遍历
-
for 循环const arr = [ { id: 1, label: "姓名", prop: "name", }, { id: 2, label: "年龄", prop: "age", }, { id: 3, label: "地址", prop: "address", }, ]; for (let i = 0; i < arr.length; i++) { console.log(i, " ", arr[i]); } // 0 { id: 1, label: '姓名', prop: 'name' } // 1 { id: 2, label: '年龄', prop: 'age' } // 2 { id: 3, label: '地址', prop: 'address' } -
for infor (let index in arr) { console.log(index, " ", arr[index]); } // 0 { id: 1, label: '姓名', prop: 'name' } // 1 { id: 2, label: '年龄', prop: 'age' } // 2 { id: 3, label: '地址', prop: 'address' } -
for offor (let item of arr) { console.log(item); } // { id: 1, label: '姓名', prop: 'name' } // { id: 2, label: '年龄', prop: 'age' } // { id: 3, label: '地址', prop: 'address' } -
forEach()- 接收一个回调函数,其参数为 item 为当前元素,index 为当前索引,array 为数组本身,无返回值
arr.forEach((item, index) => { console.log(index, " ", item); }); // 0 { id: 1, label: '姓名', prop: 'name' } // 1 { id: 2, label: '年龄', prop: 'age' } // 2 { id: 3, label: '地址', prop: 'address' } -
map()- 该方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。
const new_arr = arr.map((p) => p.id); // new_arr [ 1, 2, 3 ]
合并
-
concat()方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。const dynasty1 = ["夏", "商", "周"]; const dynasty2 = ["唐", "宋", "元", "明", "清"]; const dynasty3 = ["民国"]; let dynasty = dynasty1.concat(dynasty2, dynasty3); console.log(dynasty); // ['夏', '商', '周', '唐', '宋', '元', '明', '清', '民国'] -
...扩展运算符// 使用扩展运算符更为简洁 console.log([...dynasty1, ...dynasty2, ...dynasty3]);
去重
大致可分为两类:一是利用语法自身键不可重复性;二是使用中间变量加循环。
-
使用
Set:将数组转化为 Set 对象,Set 对象会自动去重,再转化为数组。const nums = [1, 2, 3, 4, 3, 2, 1]; const uniqueArr = [...new Set(nums)]; console.log(uniqueArr); // [1, 2, 3, 4] -
使用
forEach + includes:const removeDuplication = (arr) => { const newArr = []; arr.forEach((f) => { if (!newArr.includes(f)) { newArr.push(f); } }); return newArr; }; console.log(removeDuplication(nums)); // [1, 2, 3, 4] -
使用
filter + indexOf:遍历数组,对每个元素判断是否在新数组中出现过。const uniqueArr = nums.filter((f, index) => { return nums.indexOf(f) === index; }); console.log(uniqueArr); // [1, 2, 3, 4] -
... 还有很多种组合方式,但是有的方式有一定的缺陷性(数据类型兼容性差)
-
比如上面
filter + indexOf组合,如果数组中出现 NaN,那就处理不了了,因为当indexOf中的searchElement参数值为NaN时,indexOf()总是返回-1。 -
还比如可以将元素转为对象的属性,再利用
Object.keys方法取出属性,这样也能起到去重效果,但是元素就都变成了字符串,只能适用于数组中全是字符串元素的场景,否则还要再做转化) -
最上面两种形式适用性强,但论语法简洁度,还是最推荐第一种
-
求和
-
reduce()函数(功能很强,不仅用于数组元素求和)- 第一个参数:接收一个回调函数,其参数为:pre 为上次回调的结果,cur 为当前元素,index 为元素索引
- 第二个参数:init 为初始值,该值如果设置了,将作为第一次回调时 pre 的值,如果没有指定,pre 默认值为第一个元素的值,回调函数将从索引 1 的位置开始执行
const arr = [1, 2, 3, 4, 5]; const sum1 = arr.reduce((pre, cur) => pre + cur, (init = 0)); console.log('sum1: ', sum1); // sum1: 15 // 求和数组的元素为对象等时,初始值不可省略 const arrs = [ { id: 1, label: "第一", }, { id: 2, label: "第二", }, { id: 3, label: "第三", }, { id: 4, label: "第四", }, { id: 5, label: "第五", }, ]; const sum = arrs.reduce((pre, cur) => pre + cur.id); console.log("sum: ", sum); // sum: [object Object]2345 -
reduceRight()与reduce()用法相同,迭代顺序相反。 -
eval()函数 +join()方法 (eval 不推荐使用)const sum2 = eval(arrs.join("+")); console.log('sum2: ', sum2); // sum2: 15
筛选/过滤
-
filter()方法创建一个新数组,新数组包含通过回调函数的所有元素。- 示例 1:筛选出数组中小于等于10 的元素
const numArr = [10, 7, 8, 32, 16, 25]; const newNumArr = numArr.filter((f) => f <= 10); console.log(newNumArr); // [10, 7, 8]- 示例 2:筛选出数组中字段
showFlag值为 'TRUE' 的元素
const menuList = [ { id: 1, icon: "icon-add", path: "/add", name: "新增", showFlag: "FALSE", }, { id: 2, icon: "icon-del", path: "/del", name: "删除", showFlag: "FALSE", }, { id: 3, icon: "icon-edit", path: "/edit", name: "编辑", showFlag: "TRUE", }, ].filter((item) => { return item.showFlag !== "FALSE"; }); console.log(menuList, "menuList"); // [ // { // id: 3, // icon: 'icon-edit', // path: '/edit', // name: '编辑', // showFlag: 'TRUE' // } // ] menuList
排序/反转
排序
- `sort()` 方法用[原地算法](https://en.wikipedia.org/wiki/In-place_algorithm)对数组的元素进行排序,并返回数组。**此方法会改变原数组**
- `toSorted()` 在不改变原数组的情况下反转数组中的元素。 *兼容性:(node v20+)*
```JavaScript
const months = ["March", "Jan", "Feb", "Dec"];
const numArray = [5, 8, 11, 23, 47, 56, 89, 3, 0];
/**
* sort / toSorted
* 默认排序是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序。
* @param compareFn 定义排序顺序的函数,可选。返回值应该是一个数字,a-b 表示升序, b-a 表示降序
*
*/
// 用法一
months.sort();
console.log(months); // ["Dec", "Feb", "Jan", "March"]
numArray.sort();
console.log(numArray); // [0, 11, 23, 3, 47, 5, 56, 8, 89]
// 用法二
numArray.sort((a, b) => {
return a - b;
});
console.log(numArray);
// [0, 3, 5, 8, 11, 23, 47, 56, 89]
// 用法三
// arr 已在上面的代码块中定义
arr.sort((a, b) => {
return b.age - a.age;
});
console.log(arr);
// [
// { name: '沙和尚', kind: 'unknow', age: 1800 },
// { name: '猪八戒', kind: 'Pig', age: 1200 },
// { name: '孙悟空', kind: 'monkey', age: 600 },
// { name: '六耳猕猴', kind: 'monkey', age: 100 }
// ]
```
**注意:** 直接使用 sort() 进行排序时会把元素转换成字符串再进行比较,适用于首字母排列规则(用法一);若需要对数组元素的数值大小进行比较,可传入一个用于比较大小的函数(用法二),若要对数组对象中的某个属性值进行比较大小,则如用法三。
反转
- `reverse()` 方法就地反转数组中的元素,并返回同一数组的引用。**此方法会改变原数组**
```JavaScript
const nums = [1, 2, 3, 4, 5];
const new_nums = nums.reverse();
console.log("nums: ", nums, "\nnew_nums: ", new_nums, "\n", nums === new_nums);
// nums: [ 5, 4, 3, 2, 1 ]
// new_nums: [ 5, 4, 3, 2, 1 ]
// true
```
- `toReversed()` 在不改变原数组的情况下反转数组中的元素。 *兼容性:(node v20+)*
```JavaScript
const nums = [1, 2, 3, 4, 5];
const new_nums = nums.reverse();
console.log("nums: ", nums, "\nnew_nums: ", new_nums, "\n", nums === new_nums);
// nums: [ 1, 2, 3, 4, 5 ]
// new_nums: [ 5, 4, 3, 2, 1 ]
// false
```
浅拷贝 | 深拷贝
深浅,指的是数据嵌套的深浅。JS 中,
object是引用类型的数据, 数组array是object数据类型中的一种,它们实际不存放在栈中,而是堆中,栈中的变量标识符保存的是堆中数据的引用地址,而不是实际数据值。
正是这种复杂的数据类型和存储上的差异,导致对一个 object 类型的数据直接赋值操作,复制的仅是指向底层数据结构的指针,并不是生成了一个全新的数组。所以两个变量的引用地址相同,指向同一处空间,改了其中任意一个,另一个的数据会随之而变化。
复杂数据类型之间相互嵌套(如数组中的某元素为数组或对象类型,或对象中的某个属性值为数组或对象),如果仅对变量本身进行拷贝,堆中开辟一块新空间来保存新数据,即浅拷贝(多层嵌套只拷贝了最外面一层所以称为浅)。
而深拷贝要做到新数据与原数据毫无关联,那么如果内部有引用类型的数据,也要新开辟空间来保存,与原数据中的指向不同。这样无论嵌套多少层,都与原数据互不影响,即所谓深。
一个🌰区分两者
const obj = { name: "xiaolong" };
const arr = [1, 2, obj];
const list = arr;
const newArr = [...arr];
// list 直接赋值而来,两者引用相同地址
console.log(arr === list);
// true
// 扩展运算符实现浅拷贝,但内部元素 obj 还是同一处地址的引用
console.log("第一层:", arr === newArr, "\n第二层:", arr[2] === newArr[2]);
// 第一层: false
// 第二层: true
// 若能实现上面 arr[2] !== newArr[2],就是深拷贝了
// 下面是一个简单的递归实现(没有考虑其他一些特殊数据)
const deeplyCopy = (data) => {
if (!data || typeof data !== "object") {
return data;
}
let newData;
if (Array.isArray(data)) {
newData = [...data].map((p) => deeplyCopy(p));
} else {
newData = {};
for (let key in data) {
newData[key] = deeplyCopy(data[key]);
}
}
return newData;
};
const newDeeplyArr = deeplyCopy(arr);
console.log(newDeeplyArr);
console.log(newDeeplyArr[2]);
console.log(arr[2] === newDeeplyArr[2]);
// [ 1, 2, { name: 'xiaolong' } ]
// { name: 'xiaolong' }
// false
浅拷贝
-
使用
...扩展运算符const a = [1, 2, 3]; const b = a; const c = [...a]; a[0] = 4; console.log(b[0], c[0]); // 4 1 -
concat方法变形使用// 1. 原数组直接调用 concat 方法 const d = a.concat(); // 2. 空数组合并原数组 const e = [].concat(a); a[0] = 4; console.log(d[0], e[0]) // 1 1 -
使用
slice()方法剪切const f = a.slice(); a[0] = 4; console.log(f[0]); // 1 // 详细使用 // slice 方法返回一个新的数组对象,新数组对象的元素由参数 start 和 end 决定。 const animals = ['ant', 'bison', 'camel', 'duck', 'elephant']; const a1 = animals.slice(1); // 切掉索引 1 之前的(即从头剪掉一个)元素,返回剩余元素组成的数组 const a2 = animals.slice(2); // 切掉索引 2 之前的(即从头剪掉两个)元素,返回剩余元素组成的数组 const a3 = animals.slice(4); // 切掉索引 4 之前的(即从头剪掉四个)元素,返回剩余元素组成的数组 const a4 = animals.slice(0, 4); // 从索引 0 开始切,切到索引 4(不包括索引 4 处的元素),即切掉并返回前四个元素组成的数组,切四个(end - start) const a5 = animals.slice(2, 4); // 从索引 2 开始切,切到索引 4(不包括索引 4 处的元素),即切掉并返回索引 2、索引 3 处的元素组成的数组,切两个 console.log("a1: ", a1, "\na2: ", a2, "\na3: ", a3, "\na4: ", a4, "\na5: ", a5); // a1: [ 'bison', 'camel', 'duck', 'elephant' ] // a2: [ 'camel', 'duck', 'elephant' ] // a3: [ 'elephant' ] // a4: [ 'ant', 'bison', 'camel', 'duck' ] // a5: [ 'camel', 'duck' ] // 总结 // 1. 当 slice 方法无参数时,表示浅拷贝,返回一个数组,包含了原数组全部元素 // 2. 当 slice 方法只有一个参数时。为零如上,浅拷贝;为正整数表示从头剪掉了几个元素,返回剩余元素的集合;负整数表示从尾部剪掉几个元素,返回剪掉的元素集合。 // 3. 当 slice 方法有两个参数时,第一个参数表示起始位置,第二个参数表示终止位置(不包含)。 arr.slice() 等价于 arr.slice(0) 等价于 [ ...arr ] arr.slice(n) === arr.slice(0, n) // 0 < n <= arr.length arr.slice(arr.length) // []
深拷贝
-
类似上面写的简单递归
-
使用 JSON 字符串来转化
const newJsonArr = JSON.parse(JSON.stringify(arr)); console.log(newJsonArr[2] === arr[2]); // false -
使用第三方库,如
lodashcloneDeep 方法
数组扁平化
将多维数组转为一维数组
-
flat()方法创建一个新的数组,并根据指定深度递归地将所有子数组元素拼接到新的数组中。const arr = [1, 2, 3, 4, [5, 6, [7, [8, 9], 10]]]; const newArr = arr.flat(Infinity); console.log(newArr); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -
递归实现
const flatten = (arr) => { let newArr = []; if (Array.isArray(arr) && arr.length > 0) { arr.forEach((f) => { if (!Array.isArray(f)) { newArr.push(f); } else { newArr = newArr.concat(flatten(f)); } }); } return newArr; }; console.log(flatten(arr)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
其他操作
-
join()方法,用于将数组的所有元素连接成一个字符串并返回,用逗号或指定的分隔符字符串分隔。如果数组只有一个元素,那么将返回该元素而不使用分隔符。const wordsArr = ["Hello", "World"]; console.log(wordsArr.join()); // Hello,World console.log(wordsArr.join(" ")); // Hello World console.log(wordsArr.join("-")); // Hello-World -
some()方法,传入一个回调函数,只要有一个通过该回调函数的元素就返回 true;否则返回 false。类似
filter方法(返回通过测试的元素集合)。const numArr = [10, 7, 8, 32, 16, 25]; const newNumArr = numArr.filter((f) => f <= 10); const result = numArr.some((f) => f <= 10); console.log(newNumArr, result); // [10, 7, 8] true -
every()方法与some方法类似,只不过要求所有元素都要通过回调函数。const newNumArr1 = numArr.every((e) => e >= 7); const newNumArr2 = numArr.every((e) => e >= 8); // newNumArr1: true // newNumArr2: false -
entries()、keys()、values()生成一个可迭代数组const nums = [1, 2, 3, 4, 5]; const iterator1 = nums.entries(); const iterator2 = nums.keys(); const iterator3 = nums.values(); // 1. entries() 元素为键值对 for (const value of iterator1) { console.log(value, value instanceof Array); } // [ 0, 1 ] true // [ 1, 2 ] true // [ 2, 3 ] true // [ 3, 4 ] true // [ 4, 5 ] true // 2. keys() 元素为键名 for (const value of iterator2) { console.log(value, typeof value); } // 0 number // 1 number // 2 number // 3 number // 4 number // 3. values() 元素为键值 for (const value of iterator3) { console.log(value, typeof value); } // 1 number // 2 number // 3 number // 4 number // 5 number
还有一些平时用的不算多的方法就不在此一一列举了,感兴趣的小伙伴可以查阅 MDN 文档,也欢迎在评论区一起讨论~
感谢阅读