Skip to content

数组扩展

扩展运算符

扩展运算符(spread):三个点(...),将一个数组转为用逗号分隔的参数序列。rest参数逆运算

  • 与函数参数结合
  • 放置表达式:[...(x > 0 ? ['a'] : [])]
  • 空数组:不产生效果
  • 函数调用时,扩展运算符才能放圆括号中
js
(...[1, 2])
// Uncaught SyntaxError: Unexpected number
console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number
console.log(...[1, 2])
// 1 2

替代 apply 方法

js
// ES5
Math.max.apply(null, [14, 3, 77])
// ES6
Math.max(...[14, 3, 77])

// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))()
// ES6
new Date(...[2015, 1, 1])

扩展运算符应用

  1. 复制数组

直接赋值(arr1=arr2),只复制底层指针(浅拷贝)

以下代码a1会返回原数组的克隆,修改a2不影响a1

js
// ES5
const a1 = [1, 2]
const a2 = a1.concat()
// ES6
// 写法1
const a2 = [...a1]
// 写法2
const [...a2] = a1

a2[0] = 2
a1 // [1, 2]
  1. 合并数组

浅拷贝

js
const a1 = [{ foo: 1 }]
const a2 = [{ bar: 2 }]

const a3 = a1.concat(a2)
const a4 = [...a1, ...a2]

a3[0] === a1[0] // true
a4[0] === a1[0] // true
  1. 解构赋值

与解构赋值结合起来,用于生成数组。

扩展运算符用于数组赋值,只能放在参数的最后一位

js
// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list

// 报错 - 扩展运算符为数组赋值,只能放最后
const [first, ...middle, last] = [1, 2, 3, 4, 5];
  1. 字符串

将字符串转为真正的数组。

JavaScript 会将四个字节的 Unicode 字符,识别为 2 个字符。扩展运算符且能够正确识别四个字节的Unicode

js
'x\uD83D\uDE80y'.length // 4
[...'x\uD83D\uDE80y'].length // 3

建议:涉及到操作四个字节的 Unicode 字符的函数,用扩展运算符改写。

js
function length(str) {
  return [...str].length
}
  1. iterator 接口对象

任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。如:

js
let nodeList = document.querySelectorAll('div')
let array = [...nodeList]
  1. Map 和 Set 结构,Generator 函数
js
// Map
let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three']
])

let arr = [...map.keys()] // [1, 2, 3]
// Generator
const go = function* () {
  yield 1
  yield 2
  yield 3
}

;[...go()] // [1, 2, 3]

没有 Iterator 接口的对象,使用扩展运算符,将会报错。

js
const obj = { a: 1, b: 2 }
let arr = [...obj] // TypeError: Cannot spread non-iterable object

Array.form()

Array.from:将两类对象转为真正的数组

  • 类似数组的对象(array-like object)
  • 可遍历(iterable)的对象(Set 和 Map)

如果参数为数组,将会返回一样的新数组

js
let arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
}
// ES5
var arr1 = [].slice.call(arrayLike) // ['a', 'b', 'c']
// ES6
let arr2 = Array.from(arrayLike) // ['a', 'b', 'c']
// NodeList
let ps = document.querySelectorAll('p')
Array.from(ps).filter((p) => {})

第二参数:回调函数

js
Array.from(arrayLike, (x) => x * x)
// 等同于
Array.from(arrayLike).map((x) => x * x)

类数组对象:必须有length属性。任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。因为扩展运算符调用的是遍历器接口Symbol.iterator),如果没有部署这个接口,就无法转换。

js
Array.from({ length: 3 })
// [ undefined, undefined, undefined ]

处理各种 Unicode 字符,可以避免 JavaScript 将大于\uFFFF 的 Unicode 字符

js
function countSymbols(string) {
  return Array.from(string).length
}

polyfill

js
const toArray = (() => (Array.from ? Array.from : (obj) => [].slice.call(obj)))()

Array.of()

Array.of():将一组值,转换为数组。

  • 弥补数组构造函数Array()的不足,参数不同,行为有差异
  • 参数不少于 2 个才会组成新数组

Array 构造函数问题:参数不同,行为不同

js
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]

Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]

polyfill

js
function ArrayOf() {
  return [].slice.call(arguments)
}

数组实例

copyWithin()

copyWithin(target,start,end):将指定位置的成员复制到其他位置,然后返回当前数组。(会改变当前数组

参数说明:

  • target(必需):从该位置开始替换数据。如果为负值,表示倒数。
  • start(可选):从该位置开始选取,默认为 0。如果为负值,表示从末尾开始计算。
  • end(可选):从该位置前停止,默认等于数组长度。如果为负值,表示从末尾开始计算。

三个参数都应该是数值,如果不是,会自动转为数值。

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

// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]

// 将3号位复制到0号位
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// [empty,empty,empty,1,empty] => [1,empty,empty,1,empty]
// {0: 1, 3: 1, length: 5}

// 将2号位到数组结束,复制到0号位
let i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);
// 3 4 5 -> 0
// Int32Array [3, 4, 5, 4, 5]

// 替换写法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]

find() ,findIndex()

find(callback, bindThis):用于找出第一个符合条件的数组成员

  • 执行回调,找出第一个返回条件为 true 的成员

  • 都不符合 返回 undefined

  • 回调参数: (value, index, arr)

findIndex(callback, bindThis):返回第一个符合条件数组成员的位置

  • 都不符合,返回 -1
js
function f(v) {
  return v > this.age
}
let person = { name: 'John', age: 20 }
;[10, 12, 26, 15].find(f, person) // 26

NaN 问题:indexof 无法匹配 NaN

find、findIndex 可以匹配

js
;[NaN]
  .indexOf(NaN)
  // -1
  [NaN].findIndex((y) => Object.is(NaN, y))
// 0

fill()

fill:使用给定值,填充一个数组。(浅拷贝

  • value(必):填充数据,默认填充undefined
  • startIndex(可):起始位置
  • endIndex(可):结束位置,不包含结束位置
js
;['a', 'b', 'c'].fill(7)
// [7, 7, 7]

new Array(3).fill(7)[
  // [7, 7, 7]

  ('a', 'b', 'c')
].fill(7, 1, 2)
// ['a', 7, 'c']

entries(),keys() ,values()

entries()keys()values():用于遍历数组。它们都返回一个遍历器对象,可以用for...of循环进行遍历

区别:keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

js
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"

可以调用遍历器next方法进行遍历

js
let letter = ['a', 'b', 'c']
let entries = letter.entries()
entries.next().value // [0, 'a']
entries.next().value // [1, 'b']
entries.next().value // [2, 'c']

includes()

ES2016 - includes(value, startIndex):返回一个布尔值,表示某个数组是否包含给定的值

indexof 缺点

  • 未找到,返回值为 -1 ,不够清晰
  • 内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判

polyfill

js
const contains = (() => (Array.prototype.includes ? (arr, value) => arr.includes(value) : (arr, value) => arr.some((el) => el === value)))()
contains(['foo', 'bar'], 'baz') // => false

MapSethas方法和 includes区别

  • Maphas查找键名
  • Sethas查找值

flat(),flatMap()

flat():将嵌套的数组变成一维的数组

  1. 返回一个数组

  2. 默认拉平一层

  3. 会跳过空位

js
[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
[1, 2, , 4, 5].flat()
// [1, 2, 4, 5]

flatMap(callback, bindThis):先执行 map,然后执行 flat (map 和 flat 结合)。默认展开一层

js
arr.flatMap(function callback(currentValue[, index[, array]]) {
  // ...
}[, thisArg])

参数说明

callback:回调函数

  • currentValue:当前数组成员
  • index:数组位置
  • array:原数组

bindThis :绑定遍历函数里面的this

js
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
;[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]

at()

问题:JavaScript 不支持数组的负索引。

js
// 取最后一个值
arr[arr.length - 1]

arr.at(-1)

数组空位

数组的空位:数组的某一个位置没有任何值

空位:没有任何值,不是 undefined

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

ES5 - 空位处理

forEach(), filter(), reduce(), every()some():都会跳过空位。

map():跳过空位,但会保留这个值

join()toString():将空位视为undefined,而undefinednull会被处理成空字符串。

js
// forEach方法
[,'a'].forEach((x,i) => console.log(i)); // 1

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

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

// reduce方法
[1,,2].reduce((x,y) => x+y) // 3

// 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 - 空位处理

Array.from:将数组的空位,转为undefined

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

copyWithin():连空位一起拷贝

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

for...of:会遍历空位

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

sort() 的排序稳定性

排序稳定性(stable sorting):排序关键字相同的项目,排序前后的顺序不变。

插入排序、合并排序、冒泡排序等都是稳定的

堆排序、快速排序等是不稳定的。

不稳定排序缺点:多重排序时可能会产生问题。

稳定排序说明:假设有一个姓和名的列表,要求按照“姓氏为主要关键字,名字为次要关键字”进行排序。开发者可能会先按名字排序,再按姓氏进行排序。如果排序算法是稳定的,这样就可以达到“先姓氏,后名字”的排序效果。如果是不稳定的,就不行。

最早,Array.prototype.sort()是否稳定,由浏览器决定。ES2019 规定,Array.prototype.sort()的默认排序算法必须稳定。

相关链接

[-] 数组扩展