数组的扩展
1. 扩展运算符
1)含义
扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5扩展运算符后面还可以放置表达式。
const arr = [
...(x > 0 ? ['a'] : []),
'b',
];如果扩展运算符后面是一个空数组,则不产生任何效果。
[...[], 1]
// [1]注意,只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。
2)替代函数的 apply 方法
由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。
通过push函数,将一个数组添加到另一个数组的尾部。
上面代码的 ES5 写法中,push方法的参数不能是数组,所以只好通过apply方法变通使用push方法。有了扩展运算符,就可以直接将数组传入push方法。
3)扩展运算符的应用 *
(1)复制数组
数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
ES5 只能用变通方法来复制数组。
扩展运算符提供了复制数组的简便写法。
(2)合并数组
扩展运算符提供了数组合并的新写法。
不过,这两种方法都是浅拷贝,使用的时候需要注意。
上面代码中,a3和a4是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。
(3)与结构赋值结合
扩展运算符可以与解构赋值结合起来,用于生成数组。
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
(4)字符串
扩展运算符还可以将字符串转为真正的数组。
上面的写法,有一个重要的好处,那就是能够正确识别四个字节的 Unicode 字符。
(5)实现了 Iterator 接口的对象
任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。
上面代码中,arrayLike是一个类似数组的对象,但是没有部署 Iterator 接口,扩展运算符就会报错。这时,可以改为使用Array.from方法将arrayLike转为真正的数组。
(6)Map 和 Set 结构, Generator函数
扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。
Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。
2. Array.from()
1)含义
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
2)用法
把一个类似数组的对象,Array.from将它转为真正的数组。
实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。
只要是部署了 Iterator 接口的数据结构,Array.from都能将其转为数组。字符串和 Set 结构都具有 Iterator 接口,因此可以被Array.from转为真正的数组。
参数是一个真正的数组,Array.from会返回一个一模一样的新数组。
扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。Array.from方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
将数组中布尔值为false的成员转为0。
返回各种数据的类型。
3)模拟实现
对于还没有部署该方法的浏览器,可以用Array.prototype.slice方法替代。
3. Array.of()
1) 含义
Array.of方法用于将一组值,转换为数组。
2)用法
这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。
只有当参数个数不少于 2 个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
Array.of总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
3)模拟实现
Array.of方法可以用下面的代码模拟实现。
4. 数组实例的 copyWith()
1)含义
在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
2)用法
它接受三个参数。
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
这三个参数都应该是数值,如果不是,会自动转为数值。
5. 数组实例的 find() 和 findIndex()
1)find 含义
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
2)find 用法
find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
3)findIndex 含义
返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
4)用法
这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。
这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足。
indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。
6. 数组实例的 fill()
1)含义
fill方法使用给定值,填充一个数组。
上面代码表明,fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
2)用法
fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。
7. 数组实例的 entries(), keys(),和values()
1)含义
keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历。
8. 数组实例的 includes()
1)含义
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。
2)用法
该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。
没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值。
indexOf方法有两个缺点
不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于
-1,表达起来不够直观。它内部使用严格相等运算符(
===)进行判断,这会导致对NaN的误判。
includes使用的是不一样的判断算法,就没有这个问题。
Map 和 Set 数据结构有一个has方法,需要注意与includes区分。
Map 结构的
has方法,是用来查找键名的,比如Map.prototype.has(key)、WeakMap.prototype.has(key)、Reflect.has(target, propertyKey)。Set 结构的
has方法,是用来查找值的,比如Set.prototype.has(value)、WeakSet.prototype.has(value)。
9. 数组实例的 flat(), flatMap()
1)flat() 含义
数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
2)flat() 用法
flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。参数为2,表示要“拉平”两层的嵌套数组。
如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。
如果原数组有空位,flat()方法会跳过空位。
3) flatMap() 含义
flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。
flatMap()只能展开一层数组,返回的还是一个嵌套数组。
4)flatMap() 用法
flatMap()方法的参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。
10. 数组的空位
1)含义
数组的空位指,数组的某一个位置没有任何值。比如,Array构造函数返回的数组都是空位。
注意,空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。
2)用法
ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。
forEach(),filter(),reduce(),every()和some()都会跳过空位。map()会跳过空位,但会保留这个值join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
ES6 则是明确将空位转为undefined。
Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。
扩展运算符(...)也会将空位转为undefined。
copyWithin()会连空位一起拷贝。
fill()会将空位视为正常的数组位置。
for...of循环也会遍历空位。
entries()、keys()、values()、find()和findIndex()会将空位处理成undefined。
由于空位的处理规则非常不统一,所以建议避免出现空位。
11. Array.prototype.sort() 的排序稳定性
2)含义
排序稳定性(stable sorting)是排序算法的重要属性,指的是排序关键字相同的项目,排序前后的顺序不变。
上面代码对数组arr按照首字母进行排序。排序结果中,straw在spork的前面,跟原始顺序一致,所以排序算法stableSorting是稳定排序。
上面代码中,排序结果是spork在straw前面,跟原始顺序相反,所以排序算法unstableSorting是不稳定的。
早先的 ECMAScript 没有规定,Array.prototype.sort()的默认排序算法是否稳定,留给浏览器自己决定,这导致某些实现是不稳定的。ES2019 明确规定,Array.prototype.sort()的默认排序算法必须稳定。这个规定已经做到了,现在 JavaScript 各个主要实现的默认排序算法都是稳定的。
最后更新于
这有帮助吗?