一、扩展运算符
1、展开数组:替代函数的 apply() 方法
由于扩展运算符可以展开数组,所以不再需要apply()方法将数组转为函数的参数了。
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
// ES5 的写法
f.apply(null, args);
// ES6 的写法
f(...args);
2、复制数组
扩展运算符复制数组属于浅拷贝。
const a1 = [1, 2];
// ES5
const a2 = a1.concat();
//ES6
const a2 = [...a1]; // 写法一
const [...a2] = a1; // 写法二
3、合并数组
扩展运算符提供了数组合并的新写法。
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]
4、与解构赋值结合
将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
const list = [1, 2, 3, 4, 5];
// ES5
a = list[0], b = list.slice(1) // a=1, b=[2, 3, 4, 5]
// ES6
[a, ...b] = list // a=1, b=[2, 3, 4, 5]
5、字符串转数组
扩展运算符还可以将字符串转为真正的数组。
[...'hello']
// [ "h", "e", "l", "l", "o" ]
6、实现了 Iterator 接口的对象转数组
任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
上面代码中,querySelectorAll()方法返回的是一个NodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了 Iterator。
7、Map 和 Set 结构,Generator 函数
扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]
二、Array.from() :类数组转数组
Array.from()方法用于将两类对象转为真正的数组:类似数组的对象和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
常见的类数组有以下三种
- 字符串
- 函数的arguments
- DOM的NodeList
类数组又被成为“伪数组”,因为并不是真正的数组,只是类似于数组而已,类数组具有以下特点:
- 拥有length属性
- 可以使用下标方式访问
- 但不能使用数组的方法
在 ES5 中将类数组转换为真正的数组,可以使用 Array.prototype.slice.apply() 来实现,在 ES6 中,可以使用 Array.from() 。
// ES5
const a = "hello";
const arr = Array.prototype.slice.apply(a) // ["h","e","l","l","e"]
// ES6
const arr = Array.from(a) // ["h","e","l","l","e"]
三、Array.of() :创建数组
在 ES6 之前创建一个数组有两种方式,一种是使用构造函数 new Array(),另一种是使用数组字面量(即[ ])的方式创建,但是使用构造函数创建数组会产生一些怪异行为,例如:
const arr1 = new Array(); // 输出为[]
const arr2 = new Array(0); // 输出为[]
const arr3 = new Array(1); // 输出为[empty]
const arr4 = new Array(1,2); // 输出为[1,2]
为了解决传统new Array()方式创建的怪异行为,ES6 引入一种新的创建数组的方式,即Array.of()方法,例:
const arr1 = Array.of(); // 输出为[]
const arr2 = Array.of(0); // 输出为[0]
const arr3 = Array.of(1); // 输出为[1]
const arr4 = Array.of(1,2); // 输出为[1,2]
四、copyWithin() :复制元素
数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
// 示例:将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5]
它接受三个参数(均为数值)。
- target(必需):从该位置开始替换数据。如果为负值,表示倒数。
- start(可选):从该位置开始读取数据,默认为 0。为负值,表示从末尾开始计算。
- end(可选):到该位置前停止读取数据,默认等于数组长度。为负值,表示从末尾开始计算。
五、find(),findIndex(),findLast(),findLastIndex() :查找元素
1、find()
数组实例的find()方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
let a = [1, 4, -5, 10];
// 找出数组中第一个小于 0 的成员
a.find((n) => n < 0); // -5
// 回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组
a.find(function(value, index, arr) {
return value > 9;
}) // 10
2、findIndex()
数组实例的findIndex()方法的用法与find()方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。
3、findLast()、findLastIndex()
find()和findIndex()都是从数组的0号位,依次向后检查。ES2022 新增了两个方法findLast()和findLastIndex(),从数组的最后一个成员开始,依次向前检查,其他都保持不变。
const array = [
{ value: 1 },
{ value: 2 },
{ value: 3 },
{ value: 4 }
];
array.findLast(n => n.value % 2 === 1); // { value: 3 }
array.findLastIndex(n => n.value % 2 === 1); // 2
上面示例中,findLast()和findLastIndex()从数组结尾开始,寻找第一个value属性为奇数的成员。结果,该成员是{ value: 3 },位置是2号位。
六、fill() :填充数组
fill方法使用给定值,填充一个数组。
['a', 'b', 'c'].fill(7) // [7, 7, 7]
new Array(3).fill(7) // [7, 7, 7]
fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']
七、entries(),keys() 和 values() :遍历数组
ES6 提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历。
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']
八、includes() :判断元素是否存在
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。ES2016 引入了该方法。
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。
没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值。indexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判。
九、flat(),flatMap() :打平数组(数组扁平化)
数组扁平化,即将多维数组转化为一维数组。
1、在 ES5 中,实现数组扁平化的方法有三种:
- 递归实现
- toString() 方法
- join() 方法
2、在 ES6 中,使用 flat() 方法即可实现
语法为 arr.flat(正整数或 Infinity ),正整数表示打平几层,Infinity 表示不管多少层嵌套,都转化为一维数组。
let a = [1, 2, [3, 4], [5, [6, [7, 8]]]]
a.flat(2) // [1, 2, 3, 4, 5, 6, [7, 8]]
a.flat(Infinity) // [1, 2, 3, 4, 5, 6, 7, 8]
3、flatMap()
flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。
[2, 3, 4].flatMap((x) => [x, x * 2]) // [2, 4, 3, 6, 4, 8]
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
flatMap()只能展开一层数组。
[1, 2, 3, 4].flatMap(x => [[x * 2]]) // [[2], [4], [6], [8]]
// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
十、at() :获取索引值元素
长久以来,JavaScript 不支持数组的负索引,如果要引用数组的最后一个成员,不能写成arr[-1],只能使用arr[arr.length - 1]。
这是因为方括号运算符[]在 JavaScript 语言里面,不仅用于数组,还用于对象。对于对象来说,方括号里面就是键名,比如obj[1]引用的是键名为字符串1的键,同理obj[-1]引用的是键名为字符串-1的键。由于 JavaScript 的数组是特殊的对象,所以方括号里面的负数无法再有其他语义了,也就是说,不可能添加新语法来支持负索引。
为了解决这个问题,ES2022 为数组实例增加了at()方法,接受一个整数作为参数,返回对应位置的成员,并支持负索引。这个方法不仅可用于数组,也可用于字符串和类数组。
const arr = [5, 12, 8, 130, 44];
arr.at(2) // 8
arr.at(-2) // 130
如果参数位置超出了数组范围,at()返回undefined。
十一、toReversed(),toSorted(),toSpliced(),with() :不改变原数组
很多数组的传统方法会改变原数组,比如push()、pop()、shift()、unshift()等等。数组只要调用了这些方法,它的值就变了。现在有一个提案,允许对数组进行操作时,不改变原数组,而返回一个原数组的拷贝。
这样的方法一共有四个。
Array.prototype.toReversed() -> ArrayArray.prototype.toSorted(compareFn) -> ArrayArray.prototype.toSpliced(start, deleteCount, ...items) -> ArrayArray.prototype.with(index, value) -> Array
它们分别对应数组的原有方法。
toReversed()对应reverse(),用来颠倒数组成员的位置。toSorted()对应sort(),用来对数组成员排序。toSpliced()对应splice(),用来在指定位置,删除指定数量的成员,并插入新成员。with(index, value)对应splice(index, 1, value),用来将指定位置的成员替换为新的值。
上面是这四个新方法对应的原有方法,含义和用法完全一样,唯一不同的是不会改变原数组,而是返回原数组操作后的拷贝。
十二、group(),groupToMap() :分组
数组成员分组是一个常见需求,比如 SQL 有GROUP BY子句和函数式编程有 MapReduce 方法。现在为 JavaScript 新增了数组实例方法group()和groupToMap(),它们可以根据分组函数的运行结果,将数组成员分组。
1、group()
group()的参数是一个分组函数,原数组的每个成员都会依次执行这个函数,确定自己是哪一个组。
const array = [1, 2, 3, 4, 5];
array.group((num, index, array) => {
return num % 2 === 0 ? 'even': 'odd';
});
// { odd: [1, 3, 5], even: [2, 4] }
2、groupToMap()
groupToMap()的作用和用法与group()完全一致,唯一的区别是返回值是一个 Map 结构,而不是对象。Map 结构的键名可以是各种值,所以不管分组函数返回什么值,都会直接作为组名(Map 结构的键名),不会强制转为字符串。这对于分组函数返回值是对象的情况,尤其有用。
const array = [1, 2, 3, 4, 5];
const odd = { odd: true };
const even = { even: true };
array.groupToMap((num, index, array) => {
return num % 2 === 0 ? even: odd;
});
// Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
上面示例返回的是一个 Map 结构,它的键名就是分组函数返回的两个对象odd和even。
总之,按照字符串分组就使用group(),按照对象分组就使用groupToMap()。
十三、数组的空位
数组的空位指的是,数组的某一个位置没有任何值,比如Array()构造函数返回的数组都是空位。
Array(3) // [, , ,] 返回一个具有 3 个空位的数组
注意,空位不是undefined,某一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false
上面代码说明,第一个数组的 0 号位置是有值的,第二个数组的 0 号位置没有值。
1、ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。
forEach(),filter(),reduce(),every()和some()都会跳过空位。map()会跳过空位,但会保留这个值。join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
2、ES6 则是明确将空位转为undefined。
Array.from()、扩展运算符(...)、entries()、keys()、values()、find()和findIndex()方法会将数组的空位,转为undefined,也就是说,这些方法不会忽略空位。copyWithin()会连空位一起拷贝。fill()会将空位视为正常的数组位置。for...of循环也会遍历空位。
由于空位的处理规则非常不统一,所以建议避免出现空位。
十四、Array.prototype.sort() 的排序稳定性
排序稳定性(stable sorting)是排序算法的重要属性,指的是排序关键字相同的项目,排序前后的顺序不变。
const arr = [
'peach',
'straw',
'apple',
'spork'
];
const stableSorting = (s1, s2) => {
if (s1[0] < s2[0]) return -1;
return 1;
};
arr.sort(stableSorting)
// ["apple", "peach", "straw", "spork"]