Skip to main content

数组

常用方法

数组的创建

方法说明
Array.from(arrayLike, mapFn?, thisArg?)使用可迭代对象创建一个数组
Array.of(elem0, elem1, elem2, ...)根据任意传入的参数创建一个数组
Array.prototype.copyWithin(target, start?, end?)将数组内部某一范围的子数组覆盖到数组的其他位置

Array.from()

Array.from(arrayLike, mapFn?, thisArg?)

可以使用类数组、可迭代对象(实现了 Iterator 接口)来创建一个数组,第二个参数类似于 map 方法,第三个参数用于指定第二个回调中的 this

Array.from('foo'); // ["f", "o", "o"]

Array.from(new Set[3, 4, 5]); // [3, 4, 5]

Array.from(new Map([
["0", "hello"],
["1", "wowo"]
]));
// [["0", "hello"], ["1", "wowo"]]

Array.from([1, 2, 3], (x, i) => x * i); // [0, 2, 6]

Array.of()

Array.of(elem0, elem1, elem2, ...)

接收任意数量的参数,生成一个新数组。

Array.of(1, 2, 3, 4); // [1, 2, 3, 4]
Array.of(..."heloo"); // ["h", "e", "l", "l", "o"]

请注意它和 Array() 的区别。

Array(7); // [ , , , , , , ],每个元素都是空位,注意这里的空位并不总等价于 undefined
Array.of(7) // [7]

Array.prototype.copyWithin()

Array.prototype.copyWithin(target, start = 0, end = this.length)

在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组,该方法会修改原数组并且返回修改后的数组

它接受三个参数:

  • target(必需):从该位置开始替换数据。
  • start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
  • end(可选):到 该位置前停止 读取数据,默认等于数组长度。如果为负值,表示倒数。
[1, 2, 3, 4, 5].copyWithin(0, 3); // [4, 5, 3, 4, 5]

从下标为 3 的元素直到元素末尾的元素(4 和 5),复制到从下标为 0 开始的元素。

// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4); // [4, 2, 3, 4, 5]

// 将倒数第二个元素复制到 0 号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1); // [4, 2, 3, 4, 5]

栈与队列相关

方法说明
Array.prototype.push(ele1, ele2, ...)向数组尾部追加任意数量的元素
Array.prototype.pop()弹出并返回数组的最后一个元素
Array.prototype.unshift(ele1, ele2)向数组头部添加任意数量的元素
Array.prototype.shift()弹出并返回数组的第一个元素

Array.prototype.push()

Array.prototype.push(element1, ..., elementN)

可以传入的任意数量参数,追加到原数组末尾,该方法会 修改原数组并且返回数组的新长度

Array.prototype.pop()

Array.prototype.pop()

该方法的作用时 弹出并返回数组的最后一个元素,当数组的 length 为 0 时,会返回 undefined

Array.prototype.unshift()

Array.prototype.unshift(element1, ..., elementN)

可以将任意数量参数添加到数组的头部,该方法会 修改原数组并返回数组的新长度

Array.prototype.shift()

Array.prototype.shift()

该方法的作用是 弹出并返回数组的第一个元素,当数组的 length 为 0 时,会返回 undefined

遍历

方法说明
Array.prototype.forEach(callback(cur, idx?, arr?), thisArg?)遍历数组
Array.prototype.map(callback(cur, idx?, arr?), thisArg?)用于加工数组生成一个新数组
Array.prototype.reduce(callback(acc, cur, arr?), initV?)遍历数组返回一个累积值
Array.prototype.reduceRight()从右往左遍历数组返回一个累积值
Array.prototype.filter()保留数组中满足条件的元素,返回一个新数组
Array.prototype.every()检测数组中是否每个元素都满足指定条件
Array.prototype.some()检测数组中是否存在一个元素满足指定条件

Array.prototype.forEach()

Array.prototype.forEach(callback(curValue, index?, array?), thisArg?)

第一个参数必选,是一个回调函数 callback(currentValue, index, array),数组每含有一个有效元素,就会执行一次 callback,那些已删除或者未初始化的元素(数组空位)会被跳过。

[1, 2, 3, , 5]. forEach((value) => console.log(value)); 
// 1
// 2
// 3
// 5

上面的数组虽然有 5 个元素,但是其中有一个未初始化,是一个空位,因此会被跳过,回调只会被调用 4 次。

除了抛出异常以外,没有办法中止或跳出 forEach() 循环,但是你可以利用 已删除或者未初始化的元素(数组空位)会被跳过 这个机制减少循环执行次数。

[1, 2, 3].forEach((value, i, arr) => {
delete arr[i + 1];
console.log(value);
})
// 1
// 3

在第一次执行 callback 时,数组中的第二个元素被删除了,变成了一个空位(数组长度并不会变化),因此第二次执行 callback 时,使用的是数组中的第三个元素。

[1, 2, 3, 4, 5]. forEach((value, i, arr) => {
if(i === 1) {
arr.length = 0;
return;
}
console.log(value);
});
// 1

这样数组的回调只会被调用 1 次,因为当我们调用 arr.length = 0 时,数组的元素就被清空了。

important

与数组遍历有关的方法,会在 callback 第一次调用之前就确定了处理数组元素的范围,如果在 callback 中动态添加元素,也不会导致 callback 的执行次数增加。

Array.prototype.map()

Array.prototype.map(callback(callback(curValue, index?, array?), thisArg?)

该方法会返回一个新数组,但是不会修改原数组,所有的参数都和 Array.prototype.forEach 一样,不同的是,callback 的返回值就是新数组中的元素。

[1, 4, 9, 16].map(x => x * 2); // [2, 8, 18, 32]

Array.prototype.reduce()

Array.prototype.reduce(callback(acc, cur, idx?, arr?), initV?)

可以接收两个参数,第一个参数是回调函数,第二个参数表示回调函数中 acc (累加器)的初始值。如果没有指定初始值,那么 acc 的初始值为数组的第一个元素。该方法的返回值就是遍历完后,acc 的最终值。

你需要记住,每次 callback 的返回值就是下一次 callbackacc 的值。

下面例子是求数组中元素总和。

[1, 2, 3].reduce((acc, cur) => acc + cur); // 6

上面的代码中 callback 会被调用 2 次。

[1, 2, 3].reduce((acc, cur) => acc + cur, 0); // 6

上面的代码中 callback 会被调用 3 次。

第二个参数的有无决定了迭代次数

如果不传入第二个参数,那么第一次执行 callback 时,acc 的初始值为数组中的第一个元素,cur 的初始值为数组的第二个元素,这样迭代的次数为 arr.length - 1,因为 cur 是从第二个元素开始的。

如果传入第二个参数,那么第一次执行 callback 时,acc 的初始值即为该参数取值,cur 的初始值为第一个元素的取值,这样的迭代次数为 arr.length,因为 cur 是从第一个元素开始的。

如果数组为空且没有提供initialValue,会抛出TypeError

Array.prototype.reduceRight()

作用和 Array.prototype.reduce() 一样,不同的是,它是从右往左迭代数组。

Array.prototype.filter()

Array.prototype.filter(callback(cur, idx?, arr?), thisArg?)

该方法会返回一个新数组,数组的元素来源于那些通过 callback 测试的元素(返回 true),不会修改原数组

下例保留所有大于 10 的元素。

[3, 2, 10, 9, 11, 3].filter(x => x >= 10); // [10, 11]

Array.prototype.every()

Array.prototype.every(callback(cur, idx?, arr?), thisArg?)

该方法用于测试一个数组内所有元素是否全都通过 callback 测试(全都返回 true),如果每次迭代时,callback 都返回 true,那么该方法最终返回 true,否则返回 false

下例检测数组中的所有元素是否都大于 10。

[10, 3, 22, 99, 12, 32].every(x => x >= 10); // false
[12, 54, 18, 130, 44].every(x => x >= 10); // true

Array.prototype.some()

Array.prototype.some(callback(cur, idx?, arr?), thisArg?)

该方法用于测试一个数组内是否存在一个及其以上的元素通过 callback 测试(存在一个返回 true),如果每次迭代,callback 都返回 false,那么该方法最终返回 false,否则返回 true

下例检测数组中的所有元素是否存在一个大于 100 的元素。

[1, 0, -3, 32, 77, 91, 101, 32, 99].some(x => x >= 100);

查找

方法说明
Array.prototype.find(callback(cur, idx?, arr?), thisArg?)返回数组中满足提供的测试函数的第一个元素的值
Array.prototype.findIndex(callback(cur, idx?, arr?), thisArg?)返回数组中满足提供的测试函数的第一个元素的索引
Array.prototype.indexOf(ele, fromIdx?)从左往右查找指定元素的索引,没找到则返回 -1fromIndex 表示从哪个位置开始查找,可以取负数,表示从倒数第几个位置开始
Array.prototype.lastIndexOf(ele, fromIdx?)从右往左找到第一个指定元素的索引
Array.prototype.includes(ele, fromIdx?)判断数组是否包含 elefromIndex 和前面的方法一样
Array.prototype.at(idx)返回指定索引的元素,idx 可以为负数,表示倒数

其他方法

方法说明
Array.prototype.slice(begin = 0, end = length): any[]不会修改原数组,返回指定范围的子数组,不包括 endbeginend 可以取负值表示倒数
Array.prototype.splice(start, deleteCnt?, item1?, item2?, ...)会修改原数组,返回修改后的原数组,deleteCnt 表示从 start 开始删除的元素数量,item1 及其以后的参数会从 start 位置添加
Array.prototype.concat(arr1, arr2, ...)不会修改原数组,用于合并多个数组,最终返回一个新数组
Array.prototype.join(separator?)将数组中的所有元素连接起来,返回一个字符串,nullundefined 元素会被转换为空字符串
Array.prototype.reverse()会修改原数组,用于翻转数组,最终会返回原数组的引用
Array.prototype.flat(deep = 1)不会改变原数组,deep 表示递归深度,默认为 1,作用是返回扁平化数组
Array.prototype.flatMap()
Array.isArray()

Array.prototype.flat()

[1, 2, 3, [4, 5, 6]].flat(); // [1, 2, 3, 4, 5, 6]
[1, 2, 3, [[4, 5, 6]]].flat(); // [1, 2, 3, [4, 5, 6]]
[1, 2, 3, [[4, 5, 6]]].flat(2); // [1, 2, 3, 4, 5, 6]

如果不知道一个数组的嵌套深度,但是想让它完全扁平化,可以传入 Infinity 作为 deep

[1, [2, 3, [4, 5]], 6].flat(Infinity); // [1, 2, 3, 4, 5, 6]
该方法会移除数组的空位
[1, , 2, 3].flat(); // [1, 2, 3]

关于数组的空位

Array(3) // [, , ,]

上面代码中,Array(3)返回一个具有3个空位的数组。

注意,空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。

0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false

上面代码说明,第一个数组的0号位置是有值的,第二个数组的0号位置没有值。

ES5对空位的处理,已经很不一致了,大多数情况下会忽略空位。

  • forEach(), filter(), every()some()都会跳过空位。
  • map()会跳过空位,但会保留这个值
  • join()toString()会将空位视为undefined,而undefinednull会被处理成空字符串。
// forEach方法
[,'a'].forEach((x,i) => console.log(i)); // 1

// filter方法
['a',,'b'].filter(x => true) // ['a','b']

// every方法
[,'a'].every(x => x==='a') // true

// some方法
[,'a'].some(x => x !== 'a') // false

// map方法
[,'a'].map(x => 1) // [,1]

// join方法
[,'a',undefined,null].join('#') // "#a##"

// toString方法
[,'a',undefined,null].toString() // ",a,,"

ES6则是明确将空位转为undefined

Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。

Array.from(['a',,'b'])
// [ "a", undefined, "b" ]

扩展运算符(...)也会将空位转为undefined

[...['a',,'b']]
// [ "a", undefined, "b" ]

copyWithin()会连空位一起拷贝。

[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]

fill()会将空位视为正常的数组位置。

new Array(3).fill('a') // ["a","a","a"]

for...of循环也会遍历空位。

let arr = [, ,];
for (let i of arr) {
console.log(1);
}
// 1
// 1

上面代码中,数组arr有两个空位,for...of并没有忽略它们。如果改成map方法遍历,空位是会跳过的。

entries()keys()values()find()findIndex()会将空位处理成undefined

// entries()
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]

// keys()
[...[,'a'].keys()] // [0,1]

// values()
[...[,'a'].values()] // [undefined,"a"]

// find()
[,'a'].find(x => true) // undefined

// findIndex()
[,'a'].findIndex(x => true) // 0

由于空位的处理规则非常不统一,所以建议避免出现空位。

技巧

让类数组使用数组上的方法

类数组的定义

字符串实际上就是一个原生的类数组

数组的 length 属性是可读写的