数组
常用方法
数组的创建
| 方法 | 说明 | 
|---|---|
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 的返回值就是下一次 callback 中 acc 的值。
下面例子是求数组中元素总和。
[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?) | 从左往右查找指定元素的索引,没找到则返回 -1,fromIndex 表示从哪个位置开始查找,可以取负数,表示从倒数第几个位置开始 | 
Array.prototype.lastIndexOf(ele, fromIdx?) | 从右往左找到第一个指定元素的索引 | 
Array.prototype.includes(ele, fromIdx?) | 判断数组是否包含 ele,fromIndex 和前面的方法一样 | 
Array.prototype.at(idx) | 返回指定索引的元素,idx 可以为负数,表示倒数 | 
其他方法
| 方法 | 说明 | 
|---|---|
Array.prototype.slice(begin = 0, end = length): any[] | 不会修改原数组,返回指定范围的子数组,不包括 end,begin 和 end 可以取负值表示倒数 | 
Array.prototype.splice(start, deleteCnt?, item1?, item2?, ...) | 会修改原数组,返回修改后的原数组,deleteCnt 表示从 start 开始删除的元素数量,item1 及其以后的参数会从 start 位置添加 | 
Array.prototype.concat(arr1, arr2, ...) | 不会修改原数组,用于合并多个数组,最终返回一个新数组 | 
Array.prototype.join(separator?) | 将数组中的所有元素连接起来,返回一个字符串,null 和 undefined 元素会被转换为空字符串 | 
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,而undefined和null会被处理成空字符串。
// 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
由于空位的处理规则非常不统一,所以建议避免出现空位。
技巧
让类数组使用数组上的方法
类数组的定义
字符串实际上就是一个原生的类数组