数组
常用方法
数组的创建
方法 | 说明 |
---|---|
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
由于空位的处理规则非常不统一,所以建议避免出现空位。
技巧
让类数组使用数组上的方法
类数组的定义
字符串实际上就是一个原生的类数组