JavaScript 中常用的数组 API 总结

JavaScript 中常用的数组 API 总结

1. 数组简介

与其他语言一样,JavaScript 中数组对象的作用是使用单独的变量名来存储一系列具有一定相关性的值。但与其他编程语言不同的是,由于 JavaScript 语言本身是一种动态的弱类型语言,在 JavaScript 中的变量与任何特定值类型之间没有任何关联,任何变量都可以重新分配所有类型的值,在计算过程中涉及到不匹配的类型时允许隐式类型转换。因此,JavaScript 的数组中的每个槽位可以存储任意类型的数据。这就意味着可以创建一个每个槽位数据类型各不相同的数组,它可以的第一个元素是字符串,第二个元素是数值,第三个是对象。

JavaScript 的数组也是动态大小的,会随着数据添加而自动增长。

1.1 创建数组

  1. 构造函数方法: new Array()

    • length: 表示创建一个包含length个元素的数组,这个时候每个槽位不进行填充,为empty的状态。
    • element0,...,elementN: 以element0,...,elementN为元素创建长度为N+1的数组。
  2. 数组字面量:[]

  3. Array.from():将可迭代对象或类数组对象转换为数组实例。

  4. Array.of():将一组参数转换为数组实例。

1.2 检测数组

判断一个对象是否为数组,可以使用以下几种方式:

  1. 通过instanceof判断

instanceof 运算符用于检验构造函数的 prototype 属性是否出现在对象的原型链中的任何位置,返回一个布尔值。
需要注意的是:prototype属性是可以修改的,所以并不是最初判断为true就一定永远为真。

[] instanceof Array // -> true
{} instanceof Array // -> false
  1. 通过constructor判断

实例的构造函数属性 constructor 指向构造函数,那么通过 constructor属性也可以判断是否为一个数组。

;[1, 3, 4].constructor === Array // -> true
  1. Array.isArray()

Array.isArray() 用于确定传递的值是否是一个数组,返回一个布尔值。

Array.isArray([1, 2, 3]) // -> true
  1. 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() 静态方法从可迭代对象(mapset)或类数组对象(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)创建一个 lengthvalue 的空数组(这意味着一个由 value 个空槽组成的数组,而不是由 valueundefined 组成的数组)
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() 方法返回一个新的数组对象,这一对象是一个由 startend 决定的原数组的浅拷贝(包括 start,不包括 end),其中 startend 代表了数组元素的索引。原始数组不会被改变。

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()函数除非抛出异常,否则没有办法停止或者中断循环,如果需要提前终止,可以使用forfor...offor...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() 函数的数组本身
  • initialValuerecuce函数的初始值,可传可不传,如果传入,则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() 方法后再调用深度为 1flat() 方法(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. 遍历数组的方法

  1. for 循环

    • 优点:
      • 最基本的遍历方法,适用于所有场景。
      • 可以通过 i 索引直接访问数组元素。
      • 可以使用 breakcontinue 控制循环流程。
    • 缺点:
      • 代码相对较长和繁琐,易出错。
      • 不适用于对数组元素进行高级操作。
  2. while 循环

    • 优点:

      • 最基本的遍历方法,适用于所有场景。

      • 可以通过 i 索引直接访问数组元素。

      • 可以使用 breakcontinue 控制循环流程。

    • 缺点:

      • 容易陷入无限循环。
  3. for...in 方法

    • 缺点:
      • for ... in以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性,是为遍历对象属性而构建的,不建议与数组一起使用。
    const names = ['Peter', 'John', 'Tom']
    for (const key in names) {
      if (names.hasOwnProperty(key)) {
        console.log('Hello,', names[key])
      }
    }
    
  4. for...of 方法

    • 优点:

      • 可以使用continuebreak控制循环流程。
      • 可以直接读取元素的值
    • 缺点:

      • 不能获取元素的索引
  5. forEach 方法

    • 优点:

      • 简洁易用,适合处理简单的逻辑操作。

      • 不需要手动创建循环计数器。

    • 缺点:

      • 不能中途跳出循环。

      • 回调函数不支持异步函数。

  6. 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...infor...of 尽量不要作为遍历数组的第一选择。

6. 复制数组的方法

6.1 数组的浅拷贝

  1. ✅ 扩展运算符 [...array]
  2. array.slice()方法
  3. array.concat()
  4. Object.assign([],array)
  5. Array.from(array)
  6. 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 数组的深拷贝

  1. JSON.parse(JSON.stringify(array))
  • 优点:

    1. 简单易懂:这种方法非常简单,易于理解和实现,不需要额外的库或工具。
    2. 跨平台兼容性:JSON 是一种通用的数据格式,几乎所有编程语言都支持 JSON 解析和序列化,因此可以在不同平台和语言之间进行数据交换。
    3. 创建原始数组的一个完全独立的拷贝,包括所有嵌套的对象和数组。
  • 缺点:

    1. 性能问题:这种方法可能在处理大型数组或复杂对象时性能不佳。将数组转换为 JSON 字符串,然后再解析成对象,需要额外的时间和内存。
    2. 不支持特殊数据类型:JSON 不支持一些特殊的数据类型,例如函数正则表达式undefined 等。如果数组包含这些类型的元素,将无法正确地深拷贝它们。
    3. 循环引用问题:如果数组中存在循环引用(一个对象引用了自身),这种方法会抛出错误或进入无限递归,因为 JSON 无法表示循环引用。

综上所述,JSON.parse(JSON.stringify(array)) 是一种简单且通用的深拷贝方法,适用于大多数情况。但在处理性能要求高、包含特殊数据类型或存在循环引用的情况下,可能需要考虑其他深拷贝的方法,例如递归深拷贝或使用第三方深拷贝方法。

const arr = [
    function a() {
        console.log('111')
    },
    undefined,
    /^.$/
]
console.log(JSON.parse(JSON.stringify(arr))) // > [null, null, {}] 
  1. 递归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
}
  1. 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. 总结

  1. 写一个函数,得到各项的出现次数的对象。
// 返回如下:
{
'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))
  1. 写一个函数,将 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))

参考文献

  1. MDN 文档-JavaScript 标准内置对象-Array
  2. JavaScript 高级程序设计(第 4 版)
  3. JS 判断是否是数组的四种做法
  4. 如何从性能角度选择数组的遍历方式
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值