Appearance
数组扩展
扩展运算符
扩展运算符(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])扩展运算符应用
- 复制数组
直接赋值(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]- 合并数组
浅拷贝
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- 解构赋值
与解构赋值结合起来,用于生成数组。
扩展运算符用于数组赋值,只能放在参数的最后一位
js
// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list
// 报错 - 扩展运算符为数组赋值,只能放最后
const [first, ...middle, last] = [1, 2, 3, 4, 5];- 字符串
将字符串转为真正的数组。
JavaScript 会将四个字节的 Unicode 字符,识别为 2 个字符。扩展运算符且能够正确识别四个字节的Unicode
js
'x\uD83D\uDE80y'.length // 4
[...'x\uD83D\uDE80y'].length // 3建议:涉及到操作四个字节的 Unicode 字符的函数,用扩展运算符改写。
js
function length(str) {
return [...str].length
}- iterator 接口对象
任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。如:
js
let nodeList = document.querySelectorAll('div')
let array = [...nodeList]- 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 objectArray.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) // 26NaN 问题:indexof 无法匹配 NaN
find、findIndex 可以匹配
js
;[NaN]
.indexOf(NaN)
// -1
[NaN].findIndex((y) => Object.is(NaN, y))
// 0fill()
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') // => falseMap和Set中has方法和 includes区别
Map中has:查找键名Set中has:查找值
flat(),flatMap()
flat():将嵌套的数组变成一维的数组
返回一个新数组
默认拉平一层
会跳过空位
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 [, , ,] // falseES5 - 空位处理
forEach(), filter(), reduce(), every() 、some():都会跳过空位。
map():跳过空位,但会保留这个值
join()、toString():将空位视为undefined,而undefined和null会被处理成空字符串。
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()的默认排序算法必须稳定。