JavaScript 中常用的数组 API 总结
1. 数组简介
与其他语言一样,JavaScript 中数组对象的作用是使用单独的变量名来存储一系列具有一定相关性的值。但与其他编程语言不同的是,由于 JavaScript 语言本身是一种动态的弱类型语言,在 JavaScript 中的变量与任何特定值类型之间没有任何关联,任何变量都可以重新分配所有类型的值,在计算过程中涉及到不匹配的类型时允许隐式类型转换。因此,JavaScript 的数组中的每个槽位可以存储任意类型的数据。这就意味着可以创建一个每个槽位数据类型各不相同的数组,它可以的第一个元素是字符串,第二个元素是数值,第三个是对象。
JavaScript 的数组也是动态大小的,会随着数据添加而自动增长。
1.1 创建数组
-
构造函数方法:
new Array()
length
: 表示创建一个包含length
个元素的数组,这个时候每个槽位不进行填充,为empty
的状态。element0,...,elementN
: 以element0,...,elementN
为元素创建长度为N+1
的数组。
-
数组字面量:
[]
-
Array.from()
:将可迭代对象或类数组对象转换为数组实例。 -
Array.of()
:将一组参数转换为数组实例。
1.2 检测数组
判断一个对象是否为数组,可以使用以下几种方式:
- 通过
instanceof
判断
instanceof
运算符用于检验构造函数的 prototype
属性是否出现在对象的原型链中的任何位置,返回一个布尔值。
需要注意的是:prototype
属性是可以修改的,所以并不是最初判断为true
就一定永远为真。
[] instanceof Array // -> true
{} instanceof Array // -> false
- 通过
constructor
判断
实例的构造函数属性 constructor
指向构造函数,那么通过 constructor
属性也可以判断是否为一个数组。
;[1, 3, 4].constructor === Array // -> true
- ✅
Array.isArray()
Array.isArray()
用于确定传递的值是否是一个数组,返回一个布尔值。
Array.isArray([1, 2, 3]) // -> true
Object.prototype.toString().call()
Object.prototype.toString().call()
可以获取到对象的不同类型,例如
Object.prototype.toString.call([1, 2, 3]) === '[object Array]' // -> true
2. 数组的静态方法
数组的静态方法指声明在数组构造函数内部的方法,通过
Array.methods()
的方式调用。常用的数组静态方法有:Array.from()
、Array.isArray()
、Array.of()
。
2.1 Array.from()
Array.from()
静态方法从可迭代对象(map
、set
)或类数组对象(arguments
)创建一个新的浅拷贝的数组实例。
Array.from(arrayLike)
Array.from(arrayLike, mapFn)
Array.from(arrayLike, mapFn, thisArg)
arrayLike
: 可迭代或类数组对象mapFn
:调用数组每个元素的函数。如果提供,每个将要添加到数组中的值首先会传递给该函数,然后将mapFn
的返回值增加到数组中。thisArg
:执行mapFn
时用作this
的值。
例子:
Array.from('foo') // -> [ "f", "o", "o" ]
Array.from(
new Map([
[1, 2],
[2, 4],
[4, 8],
])
) // -> [[1, 2], [2, 4], [4, 8]]
Array.from(new Set(['foo', 'bar', 'baz', 'foo'])) // -> [ "foo", "bar", "baz" ]
function func() {
return Array.from(arguments)
}
func(1, 2, 3) // -> [ 1, 2, 3 ]
2.2 Array.isArray()
用于判断一个对象是否是一个数组
Array.isArray(value)
以下函数都将返回true
Array.isArray([])
Array.isArray([1])
Array.isArray(new Array())
Array.isArray(new Array('a', 'b', 'c', 'd'))
Array.isArray(new Array(3))
2.3 Array.of()
Array.of()
静态方法通过可变数量的参数集创建一个新的 Array 实例,不需要考虑参数的数量或类型。
Array.of(element0, element1, ... , elementN)
例子:
Array.of(1) // -> [1]
Array.of(1, 2, '3') // -> [1, 2, '3']
Array.of(undefined) // -> [undefined]
🔴 Array.of()
与 Array()
构造函数创建数组的区别:
- 对于单个参数的处理:
Array.of(value)
表示创建一个具有单个元素值为value
的数组,而Array(value)
创建一个length
为value
的空数组(这意味着一个由value
个空槽组成的数组,而不是由value
个undefined
组成的数组)
Array.of(7) // -> [7]
Array(7) // -> [ <7 empty items> ]
3. 数组的实例属性
3.1 Array.prototype.constructor
创建实例对象的构造函数。对于
Array
实例,初始值是Array
构造函数。
3.2 Array.prototype.length
反映数组中元素的数量。
4. 数组的实例方法
4.1 复制方法
复制方法指的是一类不修改原数组,而是返回一个新数组的方法。
通过构造出一个新的数组,然后填充元素来实现。
复制始终是浅层次的,即对于对象元素来说,对象的引用将被复制到新数组中,对于基本类型的元素,元素值将被复制到新的数组中。
4.1.1 Array.prototype.concat()
concat()
方法用于合并两个或多个数组,此方法不会更改现有数组,而是返回一个新数组
concat(value0, value1, ...,valueN)
例子:
const obj = {
name: 'xxx',
age: 19,
}
const array1 = ['1', obj]
const array2 = ['2', '3']
const array3 = ['4', '5']
const array4 = array1.concat() // -> ['1', {name: 'xxx', age: 19}]
const array5 = array1.concat(array2) // -> [ '1', { name: 'xxx', age: 19 }, '2', '3' ]
const array6 = array1.concat(array2, array3) // -> [ '1', { name: 'xxx', age: 19 }, '2', '3', '4', '5' ]
console.log(array4 === array1) // > false
console.log(array4.obj === array1.obj) // > true
4.1.2 Array.prototype.slice()
slice()
方法返回一个新的数组对象,这一对象是一个由start
和end
决定的原数组的浅拷贝(包括start
,不包括end
),其中start
和end
代表了数组元素的索引。原始数组不会被改变。
slice() // 不传参表示浅拷贝整个数组
slice(start) // 只传start表示从start开始浅拷贝到数组末尾
slice(start, end) // 从start开始浅拷贝到end,不包含end
例子:
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant']
console.log(animals.slice()) // > ['ant', 'bison', 'camel', 'duck', 'elephant']
console.log(animals.slice(1)) // > ['bison', 'camel', 'duck', 'elephant']
console.log(animals.slice(1, 3)) // > [ 'bison', 'camel']
4.2 填充方法
4.2.1 Array.prototype.with()
with()
方法是使用方括号表示法修改指定索引值的复制方法版本。它会返回一个新数组,其指定索引处的值会被新值替换。
array.with(index, value)
例子:
const array1 = [1, 2, 3, 4, 5]
const array2 = array1.with(2, 6)) // -> [1, 2, 6, 4, 5]
console.log(array1) // > [1, 2, 3, 4, 5]
console.log(array1 === array2) // > false
4.2.2 Array.prototype.copyWithin()
copyWithin()
方法浅复制数组的一部分到同一数组中的另一个位置并返回,不会改变原数组的长度,但会改变原数组。
copyWithin(target, start, end)
- target: 序列开始替换的目标位置,默认是 0
- start: 要复制的序列元素的起始位置
- end: 要复制的序列元素的终止位置,
copyWithin()
在拷贝的过程中不会包含该位置。
例子:
const array = ['a', 'b', 'c', 'd', 'e']
console.log(array.copyWithin()) // > ['a', 'b', 'c', 'd', 'e']
console.log(array.copyWithin(0)) // > ['a', 'b', 'c', 'd', 'e']
console.log(array.copyWithin(-2)) // > [ 'a', 'b', 'c', 'a', 'b' ]
console.log(array.copyWithin(1, 3)) // > ['a', 'd', 'e', 'd', 'e']
console.log(array.copyWithin(1, 3, 4)) // > ['a', 'd', 'c', 'd', 'e']
4.2.3 Array.prototype.fill()
fill()
方法用一个固定值填充一个数组中从起始索引(默认为0
)到终止索引(默认为array.length
)内的全部元素,返回修改后的原数组。
fill(value, start, end)
value
: 数组中要统一填充的值,不传默认为undefined
start
: 开始填充的起始位置end
: 填充的终止位置,(填充不包含该位置)
例子:
const array = [1, 2, 3, 4]
console.log(array.fill()) // > [ undefined, undefined, undefined, undefined ]
console.log(array.fill(3)) // > [3, 3, 3, 3]
console.log(array.fill(3, 1)) // > [1, 3, 3, 3]
console.log(array.fill(0, 1, 2)) // > [1, 0, 3, 4]
4.3 修改方法
修改方法指的是在操作数组的过程中会修改原数组的一类方法,返回值根据不同的操作会有所不同,有些方法返回的是相同数组的引用,有些返回的是新数组的长度或某一元素的值。
4.3.1 Array.prototype.pop()
pop()
方法从数组中删除最后一个元素,并返回该元素的值。此方法会更改数组的长度。可用于模拟出栈。
例子:
const array = [1, 2, 3, 4, 5]
console.log(array.pop()) // > 5
console.log(array) // > [1, 2, 3, 4]
4.3.2 Array.prototype.push()
push()
方法将指定的元素添加到数组的末尾,并返回新的数组长度。可用于模拟进栈。
push(value1, value2, ... , valueN)
例子:
const array = [1, 2, 3, 4, 5]
console.log(array.push(6)) // > 6
console.log(array) // > [1, 2, 3, 4, 5, 6]
console.log(array.push(7, 8, 9)) // 9
console.log(array) // > [1, 2, 3, 4, 5, 6, 7, 8, 9]
4.3.3 Array.prototype.shift()
shift()
方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。可用于模拟出队列。
例子:
const array = [1, 2, 3]
console.log(array1.shift()) // > 1
console.log(array1) // > [2, 3]
4.3.4 Array.prototype.unshift()
unshift()
方法将指定元素添加到数组的开头,并返回数组的新长度。
unshift(value1, value2, ... , valueN)
例子:
const array = [1, 2, 3, 4, 5]
console.log(array.unshift(0)) // > 6
console.log(array) // > [0, 1, 2, 3, 4, 5]
console.log(array.unshift(6, 6)) // 8
console.log(array) // > [6, 6, 0, 1, 2, 3, 4, 5]
4.3.5 Array.prototype.sort()
sort()
方法就地对数组的元素进行排序,并返回对相同数组的引用(该数组)。参数为自定义的排序顺序函数,不传参数时默认排序是将元素转换为字符串,然后按照它们的UTF-16
码元值升序排序。如果不想就地排序,则使用toSorted()
函数代替
sort()
sort(compareFn)
compareFn
: 定义排序顺序的函数,返回值应该是一个数字,其正负性表示两个元素的相对顺序,
compareFn(a, b) 的返回值 | 排序顺序 |
---|---|
> 0 | 交换位置,[b, a] |
<= 0 | 相对顺序不变, [a, b] |
function compareFn(a, b) {
if (根据排序标准,a <= b) {
return -1;
}
if (根据排序标准,a 大于 b) {
return 1;
}
}
例子:
const months = ['March', 'Jan', 'Feb', 'Dec']
months.sort()
console.log(months) // > ["Dec", "Feb", "Jan", "March"]
const array = [1, 30, 4, 21, 100000]
array.sort()
console.log(array) // > [1, 100000, 21, 30, 4]
const items = [
{ name: 'Edward', value: 21 },
{ name: 'Sharpe', value: 37 },
{ name: 'And', value: 45 },
{ name: 'The', value: -12 },
{ name: 'Magnetic', value: 13 },
{ name: 'Zeros', value: 37 },
]
// sort 万能公式
function compare(attr, rules) {
return (o1, o2) => {
if (rules === 'asc') {
return o1[attr] > o2[attr] ? 1 : -1
} else {
return o2[attr] > o1[attr] ? 1 : -1
}
}
}
console.log(items.sort(compare('name', 'asc')) // 根据每一元素的name值进行从小到大排序
console.log(items)
4.3.6 Array.prototype.reverse()
reverse()
方法就地反转数组中的元素,并返回同一数组的引用,reverse 方法会改变原数组,如果想实现在不改变原始数组的情况下反转数组中的元素,使用toReversed()
函数或者先浅拷贝一个该数组的副本再调用reverse()
。
例子:
const array = [1, 2, 3, 4, 5]
console.log(array.reverse()) // > [5, 4, 3, 2, 1]
console.log(array) // > [5, 4, 3, 2, 1]
4.3.7 Array.prototype.splice()
splice()
方法通过移除或者替换已存在的元素或添加新元素的方式就地改变一个数组的内容。该方法会改变原数组,若不想改变原数组请使用toSpliced()
,若只想访问数据的一部分内容而不修改它,使用slice()
splice(start)
splice(start, deleteCount)
splice(start, deleteCount, item1)
splice(start, deleteCount, item1, item2, ... ,itemN)
start
:表示要开始改变数组的索引值,不传默认是 0,如果小于 0,则转换为start + array.length
。deleteCount
: 表示要从数组中的start
位置开始删除的元素数量,如果不传,则默认删除到数组末尾item1 ... itemN
: 表示删除deleteCount
个数组元素后,在start
的位置开始新增的元素,不传则只执行删除元素的操作。
例子:
const months = ['Jan', 'March', 'April', 'June']
/* splice插入元素 */
months.splice(1, 0, 'Feb') // months -> ['Jan', 'Feb', 'March', 'April', 'June']
/* splice替换元素 */
months.splice(-1, 1, 'May') // months -> ['Jan', 'Feb', 'March', 'April', 'May']
/* splice删除元素 */
months.splice(0, 1) // months -> ['March', 'April', 'May']
4.3.8 总结
修改方法 | 相应的非修改方法 |
---|---|
splice() | toSpliced() |
sort() | toSorted() |
reverse() | toReversed() |
pop() | slice(-1) |
shift() | slice(0, 1) |
push(v1, v2) | concat([v1, v2]) |
unshift(v1,v2) | toSpliced(0,0,v1,v2) |
4.4 查找和定位方法
4.4.1 Array.prototype.find() & Array.prototype.findLast()
find()
和findLast()
函数都是查找数组中是否存在满足条件的元素,区别在于查找的方向不同,find()
函数是从数组的第一个元素开始查找,findLast()
是从数组的最后一个开始查找。找到符合条件的元素会提前终止迭代。没有满足条件的元素将会返回undefined
find(callbackFn)
find(callbackFn, thisArg)
例子:
const fruits = [
{ name: 'apples', quantity: 2 },
{ name: 'bananas', quantity: 0 },
{ name: 'apples', quantity: 4 },
{ name: 'cherries', quantity: 5 },
]
console.log(fruits.find((fruit) => fruit.name === 'lemons')) // > undefined
console.log(
fruits.find((fruit, index) => {
console.log(index) // > 0
return fruit.name === 'apples'
})
) // > { name: 'apples', quantity: 2 }
console.log(
fruits.findLast((fruit, index) => {
console.log(index) // > 3 2
return fruit.name === 'apples'
})
) // > { name: 'apples', quantity: 4 }
4.4.2 Array.prototype.findIndex() & Array.prototype.findLastIndex()
findIndex()
和findLastIndex()
方法返回数组中满足条件的第一个元素的索引。若没有找到对应元素则返回-1
。如果找到也会提前终止迭代。
const fruits = [
{ name: 'apples', quantity: 2 },
{ name: 'bananas', quantity: 0 },
{ name: 'apples', quantity: 4 },
{ name: 'cherries', quantity: 5 },
]
console.log(fruits.findIndex((fruit) => fruit.name === 'lemons')) // > -1
console.log(fruits.findIndex((fruit) => fruit.name === 'apples')) // > 0
console.log(fruits.findLastIndex((fruit) => fruit.name === 'apples')) // > 2
4.4.3 Array.prototype.indexOf() & Array.prototype.lastIndexOf()
indexOf()
方法返回数组中给定元素第一次出现的索引,如果不存在则返回-1
。
lastIndexOf()
返回数组中给定元素最后一次出现的索引,如果不存在,则返回-1
。
indexOf(searchElement)
indexOf(searchElement, fromIndex)
lastIndexOf(searchElement)
lastIndexOf(searchElement, fromIndex)
searchElement
: 数组中要查找的元素fromIndex
: 开始搜索的索引,在indexOf()
函数中,从fromIndex
开始向后搜索,在lastIndexOf()
函数中将从fromIndex
开始向前搜索。formIndex < 0
: 从fromIndex + array.length
开始查找fromIndex < -array.length
: 从0
开始查找。fromeIndex >= array.length
: 不会查找,直接返回-1
const beasts = ['ant', 'bison', 'camel', 'duck', 'bison']
console.log(beasts.indexOf('bison')) // > 1
console.log(beasts.lastIndexOf('bison')) // > 4
console.log(beasts.indexOf('bison', 2)) // > 4
🔴 indexOf()
与findIndex()
的异同:
- 两者都返回第一个符合条件的元素的索引值,且找到了之后直接结束迭代。找不到均返回
-1
。 indexOf()
传入的参数是元素的值,比较是执行严格的===
,findIndex()
传入的参数回调函数,返回的是回调函数返回值为true
的元素索引。indexOf()
函数接受第二个参数fromIndex
来指明开始搜索的索引,findIndex()
只能从第一个元素开始查找。
4.4.4 Array.prototype.includes()
inclueds()
函数用来判断一个数组是否包含一个特定的值,如果存在,返回true
,否则返回false
。
includes(searchElement)
includes(searchElement, fromIndex)
例子:
[1, 2, 3].includes(2) // -> true
[1, 2, 3].includes(4) // -> false
[1, 2, 3].includes(3, 3) // -> false
[1, 2, 3].includes(3, -1) // -> true
[1, 2, NaN].includes(NaN) // -> true
["1", "2", "3"].includes(3) // -> false
[1, , 3].includes(undefined)// -> true
4.4.5 总结
🔴 查找数组方法比较:
- 如果查找数组中第一次出现的满足条件的元素值,使用
find()
; 查找最后一次出现的满足条件的元素值,使用findLast()
。 - 如果查找数组中第一次出现的满足条件的元素的索引,使用
findIndex()
; 查找最后一次出现的满足条件的元素索引,使用findLastIndex()
。 - 如果查找数组中第一次出现的某个值的索引,使用
indexOf()
;查找数组中最后一次出现的某个值的索引,使用lastIndexOf()
。 - 如果判断数组中是否存在某个值,使用
includes()
。 - 如果判断数组中是否存在符合条件的元素,使用
some()
。
4.5 迭代方法
4.5.1 Array.prototype.every()
every()
方法测试一个数组内的所有元素是否都能通过指定函数的测试。它返回一个布尔值。如果数组中所有的元素均已通过制定函数的测试,则every()
函数返回的是true
,否则,当迭代到第一个测试失败的元素时,停止迭代直接返回false
。
every(callbackFn)
every(callbackFn, thisArg)
callbackFn
: 数组中每个元素执行的测试函数,返回Boolean
值,为真表示通过测试,为假表示未通过测试。
callbackFn
接受三个参数:item
: 数组中正在处理的元素index
:正在处理元素在数组中的索引array
:调用了every()
的数组本身
thisArg
: 指定执行callbackFn
函数时的this
指向
例子:
const array = [1, 30, 39, 29, 10, 13]
array.every((item) => item < 40) // -> true
array.every((item) => item < 20) // -> false
4.5.2 Array.prototype.some()
some()
方法测试数组中是否至少存在一个元素通过提供的函数实现的测试。如果在数组中找到一个元素使得提供的函数返回true
,则some()
函数返回true
;否则返回false
。它不会修改数组
例子:
const array = [1, 30, 39, 29, 10, 13]
array.some((item) => item < 20) // -> true
array.some((item) => item > 40) // -> false
console.log(array) // > [1, 30, 39, 29, 10, 13]
4.5.3 Array.prototype.filter()
filter()
方法是创建一部分通过指定函数测试的元素的浅拷贝。不会改变原数组,只会生成部分数组元素的拷贝
例子:
const word = ['apple', 'banana', 'orange', 'peach', 'lemon', 'watermelon']
const arr = word.filter((item) => item.length < 6) // -> ['apple', 'peach', 'lemon']
console.log(word) // > ['apple', 'banana', 'orange', 'peach', 'lemon', 'watermelon']
4.5.4 Array.prototype.map()
map()
方法创建一个新的数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。不会改变原数组。
例子:
const array = [1, 2, 3, 4]
const arr = array.map((item) => item * 2) // -> [2, 4, 6, 8]
console.log(array) // > [1, 2, 3, 4]
4.5.5 Array.prototype.forEach()
forEach()
方法对数组的每个元素执行一次给定的函数。forEach()
方法是一个迭代方法。它按索引升序地为数组中的每个元素调用一次提供的callbackFn
函数。与map()
不同,forEach()
函数的返回值为undefined
,而且不能继续链式调用。其典型的用法是在链式调用的末尾执行某些操作。
⚠️ 注意:
forEach()
函数期望接受的是一个同步函数,它不会等待Promise
兑现,不要在forEach()
里面使用promise
或异步函数作为回调函数。forEach()
函数除非抛出异常,否则没有办法停止或者中断循环,如果需要提前终止,可以使用for
、for...of
、for...in
这样的循环语句来代替,如果不需要进一步迭代时,可以根据需求使用every()
、some()
、find()
、findIndex()
等数组方法提前终止。
forEach(callbackFn)
forEach(callbackFn, thisArg)
例子:
❌ forEach
回调函数里面写异步代码:
const array = [1, 2, 3, 4]
array.forEach((item) => {
console.log(item)
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(item)
}, item * 1000)
})
promise.then((res) => {
console.log('async', res)
})
})
输出:
同步输出: 1 2 3 4
每隔1s输出: async 1, async 2, async 3, async 4
4.6 归并方法
4.6.1 Array.prototype.reduce()
reduce()
函数对数组中的每一项元素按序执行一个提供的reducer
函数,每一次运行reducer
函数都会将之前元素计算的结果作为参数传入,最后将其结果汇总为单个返回值。
reduce(callbackFn)
reduce(callbackFn, initialValue)
callbackFn
: 表示数组中每个元素都要执行的函数,其返回值将作为下一次调用callbackFn
函数时的accumulator
参数。对于最后一次调用,返回值将作为reduce()
函数的返回值。
该函数接受以下四个参数:accumulator
:上一次调用callbackFn
时的返回结果。对于第一次调用callbackFn
,如果指定了initialValue
的值,则accumulator
的值为initialValue
,否则,值为array[0]
。currentValue
: 当前元素的值。currentIndex
: 当前元素在数组中的索引位置。array
:调用了reduce()
函数的数组本身
initialValue
:recuce
函数的初始值,可传可不传,如果传入,则initialValue
将作为第一次调用callbackFn
函数时的accumulator
参数的值。如果不传,则一次调用callbackFn
时,将以数组中第一个元素的值作为accumulator
的值,且迭代从第二个元素开始执行。
例子:
const array = [1, 2, 3, 4, 5]
console.log(
array.reduce((acc, cur, idx) => {
console.log(idx) // > 0 1 2 3 4
return acc + cur
}, 0)
) // > 15
console.log(
array.reduce((acc, cur, idx) => {
console.log(idx) // > 1 2 3 4
return acc + cur
})
) // > 15
4.6.2 Array.prototype.reduceRight()
与
reduce
函数原理相同,只不过执行顺序是从最后一个元素到第一个元素。
4.7 迭代器方法
4.7.1 Array.prototype.entries()
entries()
方法返回一个新的数组迭代器对象,该对象包含数组中每个索引的键/值对。
例子:
const array = ['a', 'b', 'c']
const iterator = array.entries()
console.log(iterator.next()) // > { value: [ 0, 'a' ], done: false }
console.log(iterator.next()) // > { value: [ 1, 'b' ], done: false }
console.log(iterator.next()) // > { value: [ 2, 'c' ], done: false }
console.log(iterator.next()) // > { value: undefined, done: true }
4.7.2 Array.prototype.keys()
keys()
方法返回一个新的数组迭代器对象,其中包含数组中每个索引的键。
例子:
const array = ['a', 'b', 'c']
const iterator = array.keys()
console.log(iterator.next()) // > { value: 0, done: false }
console.log(iterator.next()) // > { value: 1, done: false }
console.log(iterator.next()) // > { value: 2, done: false }
console.log(iterator.next()) // > { value: undefined, done: true }
4.7.3 Array.prototype.values()
values()
方法返回一个新的数组迭代器对象,其中包含数组中每个元素的值。
例子:
const array = ['a', 'b', 'c']
const iterator = array.values()
console.log(iterator.next()) // > { value: 'a', done: false }
console.log(iterator.next()) // > { value: 'b', done: false }
console.log(iterator.next()) // > { value: 'c', done: false }
console.log(iterator.next()) // > { value: undefined, done: true }
4.8 其他方法
4.8.1 Array.prototype.at()
at()
方法接收一个整数值并返回该索引对应的元素,参数允许正数和负数。参数为负整数时表示从数组中的最后一个元素开始倒数
若index < 0
, 则会访问index + array.length
位置的元素。
一般使用array[index]
替代。
const array = [1, 2, 3, 4, 5, 6]
const index1 = 0
const index2 = -1
console.log(array.at(index1)) // > 1
console.log(array.at(index2)) // > 6
console.log(array[index2]) // > 6
4.8.2 Array.prototype.flat()
flat()方法是数组扁平化的方法,根据指定的深度将数组展平。该方法属于复制方法,并不会改变原数组,而是返回一个数组的浅拷贝。
flat()
flat(depth)
depth
:表示要展平嵌套数组的深度,默认值为1
,可传入Infinity
来实现数组的完全扁平。
例子:
console.log([1, 2, [3, 4, [5, 6]]].flat()) // > [1, 2, 3, 4, [5, 6]]
console.log([(1, 2, [3, 4, [5, 6]])].flat(2)) // > [1, 2, 3, 4, 5, 6]
console.log([(1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]])].flat(Infinity)) //> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
4.8.3 Array.prototype.flatMap()
flatMap()
方法对数组中的每个元素指定给定的回调函数,然后将结果展开一级,并返回一个新数组。等价于数组调用map()
方法后再调用深度为1
的flat()
方法(arr.map(...args).flat()
),但比分别调用这两个方法更高效一些。
flatMap(callbackFn)
flatMap(callbackFn, thisArg)
例子:
const arr = [1, 2, 3, 4]
arr.map((x) => [x, x * 2]) // -> [[1, 2], [2, 4], [3, 6], [4, 8]]
arr.flatMap((x) => [x, x * 2]) // -> [1, 2, 2, 4, 3, 6, 4, 8]
4.8.4 Array.prototype.join()
join()
方法是将一个数组中的所有元素连接成一个字符串并返回;字符串转为数组的方法:split()
join()
join(separator)
separator
: 用于指定一个字符串来分隔数组中的每一个元素,如果省略,默认使用,
分隔。传入空字符串''
则所有元素之间都没有任何字符。
例子:
const array = ['Hello', 'World']
array.join() // -> Hello,World
array.join('') // -> HelloWorld
array.join(' ') // -> Hello World
array.join('@') //-> Hello@World
4.8.5 Array.prototype.toReversed()
toReversed()
方法是reverse()
方法对应的复制版本,不会改变原数组,会返回与原数组元素顺序相反的新数组
const array = [1, 2, 3]
console.log(array.toReversed()) // > [3, 2, 1]
console.log(array) // > [1, 2, 3]
4.8.6 Array.prototype.toSorted()
toSorted()
方法是sort()
方法对应的复制版本,不会改变原数组,会返回与排序后的新数组
4.8.7 Array.prototype.toSpliced()
toSpliced()
方法是splice()
方法对应的复制版本,不会改变原数组,会返回一个新数组,并在给定的索引处删除或替换了一些元素。
4.8.8 Array.prototype.toString()
toString()
方法返回一个字符串,表示指定的数组及其元素,数组的toString
方法实际上在内部调用了join()
方法来拼接数组并返回一个包含所有数组元素的字符串,元素之间用逗号分隔。如果join()
方法不可用或者不是函数,则会使用Object.prototype.toString
来代替,并返回[object Array]
。
const arr = [1, 2]
console.log(arr.toString()) // > 1, 2
arr.join = 1 // 将 `join` 重新赋值为非函数的值
console.log(arr.toString()) // > [object Array]
console.log(Array.prototype.toString.call({ join: () => 3 })) // > 3
5. 遍历数组的方法
-
for
循环- 优点:
- 最基本的遍历方法,适用于所有场景。
- 可以通过
i
索引直接访问数组元素。 - 可以使用
break
和continue
控制循环流程。
- 缺点:
- 代码相对较长和繁琐,易出错。
- 不适用于对数组元素进行高级操作。
- 优点:
-
while
循环-
优点:
-
最基本的遍历方法,适用于所有场景。
-
可以通过
i
索引直接访问数组元素。 -
可以使用
break
和continue
控制循环流程。
-
-
缺点:
- 容易陷入无限循环。
-
-
for...in
方法- 缺点:
for ... in
以任意顺序迭代一个对象的除Symbol
以外的可枚举属性,包括继承的可枚举属性,是为遍历对象属性而构建的,不建议与数组一起使用。
const names = ['Peter', 'John', 'Tom'] for (const key in names) { if (names.hasOwnProperty(key)) { console.log('Hello,', names[key]) } }
- 缺点:
-
for...of
方法-
优点:
- 可以使用
continue
和break
控制循环流程。 - 可以直接读取元素的值
- 可以使用
-
缺点:
- 不能获取元素的索引
-
-
forEach
方法-
优点:
-
简洁易用,适合处理简单的逻辑操作。
-
不需要手动创建循环计数器。
-
-
缺点:
-
不能中途跳出循环。
-
回调函数不支持异步函数。
-
-
-
map
方法-
优点:
- 返回一个新数组,可以对原数组元素进行映射转换。
-
缺点:
- 适用于需要返回新数组的场景,不适合只需要依次处理数组元素的场景。
-
🔴 在相同运行环境下的数组遍历方法的性能比较:
const array = [...Array(100000).keys()]
//测试函数
function runningTime(fn, fnName) {
const start = new Date().getTime()
if (fn) fn()
const end = new Date().getTime()
console.log(`${fnName}执行耗时:${end - start}ms`)
}
function forfn() {
for (let i = 0, len = array.length; i < len; i++) {}
}
function forin() {
for (const key in array) {
if (array.hasOwnProperty(key));
}
}
function forof() {
for (const item of array);
}
function whilefn() {
const len = array.length
let i = 0
while (i < len) {
i++
}
}
function forEachfn() {
array.forEach((item) => {})
}
function mapfn() {
array.map((item) => {})
}
runningTime(forfn, 'for') // > for执行耗时:2ms
runningTime(whilefn, 'whilefn') // > whilefn执行耗时:1ms
runningTime(forin, 'forin') // > forin执行耗时:10ms
runningTime(forof, 'forof') // > forof执行耗时:8ms
runningTime(forEachfn, 'forEachfn') // > forEachfn执行耗时:2ms
runningTime(mapfn, 'mapfn') // > mapfn执行耗时:3ms
结论:
- 简单遍历数组优先选择
for
循环、while
循环和forEach()
,使用forEach()
的时候注意不要使用异步函数。 - 需要对数组元素进行映射的选择
map()
。 for...in
、for...of
尽量不要作为遍历数组的第一选择。
6. 复制数组的方法
6.1 数组的浅拷贝
- ✅ 扩展运算符
[...array]
array.slice()
方法array.concat()
Object.assign([],array)
Array.from(array)
array.map(item => item)
const array1 = [
1,
{
a: 1,
b: 2,
},
]
const array2 = [...array1]
const array2 = array1.slice()
const array2 = array1.concat()
const array2 = Object.assign([], array1)
const array2 = Array.from(array1)
const array2 = array1.map((item) => item)
array1[0] = 2
array1[1].a = 3
console.log(array1) // > [2, { a: 3, b: 2 }]
console.log(array2) // > [ 1, { a: 3, b: 2 } ]
6.2 数组的深拷贝
JSON.parse(JSON.stringify(array))
-
优点:
- 简单易懂:这种方法非常简单,易于理解和实现,不需要额外的库或工具。
- 跨平台兼容性:JSON 是一种通用的数据格式,几乎所有编程语言都支持 JSON 解析和序列化,因此可以在不同平台和语言之间进行数据交换。
- 创建原始数组的一个完全独立的拷贝,包括所有嵌套的对象和数组。
-
缺点:
- 性能问题:这种方法可能在处理大型数组或复杂对象时性能不佳。将数组转换为
JSON
字符串,然后再解析成对象,需要额外的时间和内存。 - 不支持特殊数据类型:
JSON
不支持一些特殊的数据类型,例如函数、正则表达式、undefined 等。如果数组包含这些类型的元素,将无法正确地深拷贝它们。 - 循环引用问题:如果数组中存在循环引用(一个对象引用了自身),这种方法会抛出错误或进入无限递归,因为
JSON
无法表示循环引用。
- 性能问题:这种方法可能在处理大型数组或复杂对象时性能不佳。将数组转换为
综上所述,JSON.parse(JSON.stringify(array))
是一种简单且通用的深拷贝方法,适用于大多数情况。但在处理性能要求高、包含特殊数据类型或存在循环引用的情况下,可能需要考虑其他深拷贝的方法,例如递归深拷贝或使用第三方深拷贝方法。
const arr = [
function a() {
console.log('111')
},
undefined,
/^.$/
]
console.log(JSON.parse(JSON.stringify(arr))) // > [null, null, {}]
- 递归
deepClone
方法
function deepClone(originValue) {
// 如果不是对象类型或是null则直接将当前值返回
if (typeof originValue !== 'object' || originValue == null) {
return originValue
}
const newObject = Array.isArray(originValue) ? [] : {}
for (const key in originValue) {
// 因为javascript没有将hasOwnProperty作为一个敏感词,所以我们很有可能将对象的一个属性命名为hasOwnProperty,
// 这样一来就无法再使用对象原型的 hasOwnProperty 方法来判断属性是否是来自原型链。
// 所以采用 Object.prototype.hasOwnProperty.call代替originValue.hasOwnProperty()
if (Object.prototype.hasOwnProperty.call(originValue, key)) {
// 若是对象类型属性,递归调用deepClone,若是其他类型属性,则直接赋值
if (typeof originValue[key] === 'object' && originValue[key] !== null) {
newObject[key] = deepClone(originValue[key])
} else {
newObject[key] = originValue[key]
}
}
}
return newObject
}
lodash
库中的cloneDeep
函数
在JavaScript
中,可以使用第三方库或函数来执行深拷贝操作。一个常用的第三方深拷贝函数是lodash
库中的cloneDeep
函数。
- 安装
lodash
库
npm install lodash
# 或者
yarn add lodash
- 导入并使用
cloneDeep
函数来执行深拷贝操作:
const _ = require('lodash')
const originalObject = {
foo: 'bar',
nested: {
baz: 'qux',
},
}
const clonedObject = _.cloneDeep(originalObject)
console.log(clonedObject) // 深拷贝后的对象
7. 总结
- 写一个函数,得到各项的出现次数的对象。
// 返回如下:
{
'111': 2,
'222': 2,
'444': 1
}
实现:
const stringArr = ['111', '222', '111', '222', '444']
const countFn = function (arr) {
const obj = {}
arr.forEach((element) => {
if (Object.prototype.hasOwnProperty.call(obj, element)) {
obj[element] += 1
} else {
obj[element] = 1
}
})
return obj
}
const countReduce = function (arr) {
return arr.reduce((pre, cur) => {
if (pre[cur]) {
pre[cur]++
} else {
pre[cur] = 1
}
return pre
}, {})
}
console.log(countFn(stringArr))
console.log(countReduce(stringArr))
- 写一个函数,将 type 相同的当在同一个数组
// 返回如下:
;[
[
{
id: 1,
type: 1,
name: '张三',
},
],
[
{
id: 2,
type: 2,
name: '李四',
},
{
id: 3,
type: 2,
name: '王五',
},
],
[
{
id: 4,
type: 3,
name: '张三',
},
],
]
实现:
const testArr = [
{
id: 1,
type: 1,
name: '张三',
},
{
id: 2,
type: 2,
name: '李四',
},
{
id: 3,
type: 2,
name: '王五',
},
{
id: 4,
type: 3,
name: '张三',
},
]
const testObj = function (arr) {
const map = {}
arr.forEach((element) => {
const type = element.type
if (Object.prototype.hasOwnProperty.call(map, type)) {
map[type].push(element)
} else {
map[type] = [element]
}
})
return Object.values(map)
}
const testMap = function (arr) {
const map = new Map()
arr.forEach((element) => {
const type = element.type
if (map.has(type)) {
map.get(type).push(element)
} else {
map.set(type, [element])
}
})
return [...map.values()]
}
const testReduce = function (arr) {
const result = arr.reduce((pre, cur) => {
if (pre[cur.type]) {
pre[cur.type].push(cur)
} else {
pre[cur.type] = [cur]
}
return pre
}, {})
return Object.values(result)
}
console.log(testObj(testArr))
console.log(testMap(testArr))
console.log(testReduce(testArr))