[JavaScript之语法和API-2]Array

157 阅读34分钟

创建数组

let a = [5,6,7] // 字面量方式
let a = new Array() // 构造函数
let a = Array() // 可以省略 new 

// 需要注意的是,将数字作为参数,会创建长度为2的空数组
let a = Array(2)
console.log(a) // [empty × 2]

静态方法

Array.of()将参数转换为数组实例

创建一个具有可变数量参数的新数组实例,而不考虑参数的数量和类型。

这个方法用于替代 ES6 之前常用的Array.prototype.slice.call(arguments)arguments对象转换为数组的写法。

conconsole.log(Array.of(5)) // [5]
console.log(Array.of("hello", 5, function foo() { })) // ["hello", 5, ƒ]

Array.from()将类数组结构转换为数组实例

从一个类数组(可迭代对象 或者 有一个 length 属性和可索引属性的任意对象)对象创建一个新的,浅拷贝的数组实例。

  • arraylike 想要转换成数组的伪数组对象或可迭代对象。
  • mapFn 如果指定了该参数,新数组中的每个元素会执行该回调函数。
  • thisArg 可选参数,执行回调函数 mapFnthis对象。
// 从 String 生成数组
console.log(Array.from('foobar')) // ["f", "o", "o", "b", "a", "r"]

// 从 Set 生成数组
const set = new Set(['foo', 'bar', 'baz', 'foo'])
console.log(Array.from(set))// ["f", "o", "o", "b", "a", "r"]

// 从 Map 生成数组
const map = new Map([[1, 2], [2, 4], [4, 8]])
console.log(Array.from(map)) // [[1, 2], [2, 4], [4, 8]]
console.log(Array.from(map.keys())) // [1, 2, 4]
console.log(Array.from(map.values())) // [2, 4, 8]

// 从类数组对象(arguments)生成数组
function f() {
    return Array.from(arguments)
}
console.log(f(1, 'foo', [6, 7])) // [1, "foo", [6, 7]]

实例方法

修改器方法(改变原数组)

pop()删除数组最后一项

从数组中删除最后一个元素,并返回该元素的值。

const colors = ['red','green','blue','orange']
console.log(colors.pop()) // orange
console.log(colors) // ["red", "green", "blue"]

const arr = []
console.log(arr.pop()) // undefined

push()数组末尾插入元素

将一个或多个元素添加到数组的末尾,并返回该数组的新长度。

const colors = ['red', 'green']
console.log(colors.push('blue', 'orange')) // 4
console.log(colors) // ["red", "green", "blue", "orange"]

unshift()数组开头插入元素

将一个或多个元素添加到数组的开头,并返回该数组的新长度。

const arr = [1, 2, 3, 4, 5]
console.log(arr.unshift(6, 7))
console.log(arr) // [6, 7, 1, 2, 3, 4, 5]

shift()删除数组第一个元素

从数组中删除第一个元素,并返回该元素的值。

const arr = [1, 2, 3, 4, 5]
console.log(arr.shift()) // 1
const arr2 = []
console.log(arr2.shift()) // undefined

splice()添加/删除数组元素

通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。

  • start 指定修改的开始位置(从0计数)。
    • 正数,超过 array.length,从数组末尾开始添加内容;
    • 负数,从数组末位开始的第几位(等价于 array.length-n);
    • 负数,绝对值大于数组长度,则表示开始位置为第 0 位。
  • deleteCount 可选 整数,表示要移除的数组元素的个数。
    • deleteCount 大于等于 start之后的元素的总数(array.length-start),则从 start后面的元素都将被删除(含第start位);
    • deleteCount 被省略了,那么start之后数组的所有元素都会被删除;
    • deleteCount是 0 或者负数,则不移除元素。这种情况下,至少应添加一个新元素。
  • item1, item2, ... 可选 要添加进数组的元素,从start 位置开始。如果不指定,则 splice() 将只删除数组元素。
//删除
let colors = ['red', 'green', 'blue', 'brown', 'orange']
let removed = colors.splice(1, 1)
console.log(colors) // ["red", "blue", "brown", "orange"]

let colors1 = ['red', 'green', 'blue', 'brown', 'orange']
let removed1 = colors1.splice(0, 8)
console.log(colors1) // [] deleteCount 大于 start ,所有元素都被删除

let colors2 = ['red', 'green', 'blue', 'brown', 'orange']
let removed2 = colors2.splice(0, 5)
console.log(colors2) // [] deleteCount 等于 start ,所有元素都被删除

let colors3 = ['red', 'green', 'blue', 'brown', 'orange']
let removed3 = colors3.splice(0)
console.log(colors3) // [] 省略 deleteCount ,所有元素都被删除

//插入
let colors = ['red']
let insert = colors.splice(0, 0, 'green', 'blue')
console.log(colors) // ["green", "blue", "red"]

let colors1 = ['red']
let insert1 = colors1.splice(5, 0, 'green', 'blue')
console.log(colors1) // ["red", "green", "blue"] start 大于 array.length ,数组末尾开始添加

let colors2 = ['red', 'green', 'blue']
let insert2 = colors2.splice(-2, 0, 'brown', 'orange')
console.log(colors2) // ["red", "brown", "orange", "green", "blue"] start 负数 从后数 start 个数(-1开始计数)的位置开始添加

let colors3 = ['red']
let insert3 = colors3.splice(-5, 0, 'yellow', 'brown')
console.log(colors3) // ["yellow", "brown", "red"] start 负数 绝对值大于数组长度,从 0 位开始添加

//替换
let colors = ['red']
let replace = colors.splice(0, 1, 'green', 'blue')
console.log(colors) // ["green", "blue"] 删除 1 项 , 插入 2 项

let colors1 = ['red', 'green', 'blue']
let replace1 = colors1.splice(0, 2, 'orange')
console.log(colors1) // ["orange", "blue"] 删除 2 项,插入 1 项

sort()对数组元素进行排序

用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的。

  • compareFunction 可选 用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。
    • firstEl 第一个用于比较的元素。
    • secondEl 第二个用于比较的元素。

如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。假设 a b 是两个将要被比较的元素:

  • 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前。
  • 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。
  • 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。
  • compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
//比较字符串函数
function compare(a, b) {
    if (a < b) {
        return -1
    }
    if (a > b) {
        return 1
    }
    return 0
}
//比较数字函数
function compareNumbers(a, b) {
    return a - b 
}
// 没指明比较函数的情况
let num = [1, 5, 37, 80, 9]
console.log(num.sort()) // [1, 37, 5, 80, 9] 按照 Unicode 位点,35 在 5 前面,80 在 9 前面

//有指明比较函数的情况
let num = [1, 5, 37, 80, 9]
console.log(num.sort()) // [1, 37, 5, 80, 9]
console.log(num.sort((a, b) => a - b)) // [1, 5, 9, 37, 80] b-a 降序

reverse()颠倒数组元素的位置

将数组中元素的位置颠倒,并返回该数组。

let arr = ['one', 'two', 'three']
console.log('arr:', arr) // ["one", "two", "three"]
let reversed = arr.reverse()
console.log('reverse:', reversed) // ["three", "two", "one"]
console.log('arr:', arr) // ["three", "two", "one"]

copyWithin()复制数组的一部分到其它位置(ES6)

浅复制数组的一部分到同一数组中的另一个位置,并返回它。

  • target 0 为基底的索引,复制序列到该位置。
    • 如果是负数,target 将从末尾开始计算。
    • 如果 target 大于等于 arr.length,将会不发生拷贝。
    • 如果 targetstart 之后,复制的序列将被修改以符合 arr.length
  • start 0 为基底的索引,开始复制元素的起始位置。
    • 如果是负数,start 将从末尾开始计算。
    • 如果 start 被忽略,copyWithin 将会从0开始复制。
  • end 0 为基底的索引,开始复制元素的结束位置。
    • copyWithin 将会拷贝到该位置,但不包括 end 这个位置的元素。
    • 如果是负数, end 将从末尾开始计算。
    • 如果 end 被忽略,copyWithin 方法将会一直复制至数组结尾(默认为 arr.length)。
// target
let arr = ['a', 'b', 'c', 'd', 'e']
console.log(arr.copyWithin(0, 3, 4)) // ["d", "b", "c", "d", "e"] 正常拷贝 不包括 end 元素
let arr2 = ['a', 'b', 'c', 'd', 'e']
console.log(arr2.copyWithin(6, 3, 4)) // ["a", "b", "c", "d", "e"] target 大于 arr.length 不发生拷贝
let arr3 = ['a', 'b', 'c', 'd', 'e']
console.log(arr3.copyWithin(4, 3, 4)) // ["a", "b", "c", "d", "d"] target 大于 start 忽略 start 之前的元素
// start
let arr = ['a', 'b', 'c', 'd', 'e']
console.log(arr.copyWithin(0, -3, 4)) // ["c", "d", "c", "d", "e"] start 从末尾开始计算 -3 也就是 c
let arr2 = ['a', 'b', 'c', 'd', 'e']
console.log(arr2.copyWithin(2)) // ["a", "b", "a", "b", "c"] start 被忽略,copywithin 从 0 开始复制
// end
let arr = ['a', 'b', 'c', 'd', 'e']
console.log(arr.copyWithin(2, 3, 4)) // ["a", "b", "d", "d", "e"] end 不包括这个元素 target c 复制为 d 
let arr2 = ['a', 'b', 'c', 'd', 'e']
console.log(arr2.copyWithin(2, 0, -4)) // ["a", "b", "a", "d", "e"] end -4 从末尾开始计算,c 复制为 a
let arr3 = ['a', 'b', 'c', 'd', 'e']
console.log(arr3.copyWithin(2, 3)) // ["a", "b", "d", "e", "e"] end 被忽略,一直复制到结尾 target c 复制 start d 到末尾 e 

fill()固定值填充数组(ES6)

  • value 用来填充数组元素的值。
  • start 可选 起始索引,默认值为0。
  • end 可选 终止索引,默认值为 this.length
let arr = [1, 2, 3, 4, 5]
console.log(arr.fill(6)) // [6, 6, 6, 6, 6]
let arr2 = [1, 2, 3, 4, 5]
console.log(arr2.fill(7, 1, 3)) // [1, 7, 7, 4, 5] 7 填充 1-3 的元素
let arr3 = [1, 2, 3, 4, 5]
console.log(arr3.fill(8, 3)) // [1, 2, 3, 8, 8] 8 填充 大于 3 的元素
let arr4 = [1, 2, 3, 4, 5]
console.log(arr4.fill(9, -4, -1)) // [1, 9, 9, 9, 5] 9 填充 length+start 到 length+end 的元素
let arr5 = [1, 2, 3, 4, 5]
console.log(arr5.fill(8, 3)) // [1, 2, 3, 8, 8] 8 填充 大于 3 的元素
let arr6 = [1, 2, 3, 4, 5]
console.log(arr6.fill(6, 3, 10)) // [1, 2, 3, 6, 6] 6 填充可用部分

访问器方法(不改变原数组)

toString()数组转字符串

返回一个字符串,表示指定的数组及其元素。

let colors = ["red", "blue", "green"]
console.log(colors.toString()) // red,blue,green

toLocaleString()数组转字符串

返回一个字符串表示数组中的元素。数组中的元素将使用各自的 toLocaleString 方法转成字符串,这些字符串将使用一个特定语言环境的字符串(例如一个逗号 ",")隔开。

  • locales 可选 带有BCP 47语言标记的字符串或字符串数组,关于locales参数的形式与解释,请看Intl页面。
  • options 可选 一个可配置属性的对象,对于数字 Number.prototype.toLocaleString(),对于日期Date.prototype.toLocaleString()
let arr = [1, "a", new Date('21 Dec 1997 9:13:00 UTC')]
const localeString = arr.toLocaleString('en', { timeZone: 'UTC' })
console.log(localeString) // 1,a,12/21/1997, 9:13:00 AM

join()数组转字符串

将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。

  • separator 可选 指定一个字符串来分隔数组的每个元素。如果需要,将分隔符转换为字符串。如果缺省该值,数组元素用逗号(,)分隔。如果separator是空字符串(""),则所有元素之间都没有任何字符。
let colors = ['red', 'green', undefined, null, 'blue']
console.log(colors.join()) // red,green,,,blue undefined null 转换为空字符串
console.log(colors.join('-')) // red-green---blue
console.log(colors.join('+')) // red+green+++blue

如果一个元素为 undefinednull,它会被转换为空字符串。

concat() 合并数组

用于合并两个或多个数组。

  • value N可选 数组和/或值,将被合并到一个新的数组中。如果省略了所有 valueN 参数,则 concat 会返回调用此方法的现存数组的一个浅拷贝。
let num1 = [[1]]
let num2 = [2, [3]]
let num3 = [5,[6]]
let nums = num1.concat(num2, num3)
console.log(nums) // [1, 2, 3, 4, 5, 6, 7, 8, 9]

//合并嵌套数组
let num1 = [[1]]
let num2 = [2, [3]]
let num3 = [5, [6]]
let nums = num1.concat(num2, num3)
console.log(nums) // [[1],2,[3],5,[6]]

slice()浅拷贝原数组元素

返回一个新的数组对象,这一对象是一个由 beginend 决定的原数组的浅拷贝(包括 begin,不包括end)。

  • begin 可选 提取起始处的索引(从 0 开始),从该索引开始提取原数组元素。
    • 如果该参数为负数,则表示从原数组中的倒数第几个元素开始提取,slice(-2) 表示提取原数组中的倒数第二个元素到最后一个元素(包含最后一个元素)。
    • 如果省略 begin,则 slice 从索引 0 开始。
    • 如果 begin 超出原数组的索引范围,则会返回空数组。
  • end 可选 提取终止处的索引(从 0 开始),在该索引处结束提取原数组元素。
    • slice 会提取原数组中索引从 beginend 的所有元素(包含 begin,但不包含 end)。
    • 如果该参数为负数, 则它表示在原数组中的倒数第几个元素结束抽取。
    • 如果 end 被省略,则 slice 会一直提取到原数组末尾。
    • 如果 end 大于数组的长度,slice 也会一直提取到原数组末尾。
let fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']
let citrus = fruits.slice(1, 3)
console.log(citrus) // ["Orange", "Lemon"]

//类数组对象
function list() {
    return Array.prototype.slice.call(arguments)
}
let list1 = list(1, 2, 3)
console.log(list1) // [1, 2, 3]

indexOf()返回指定定元素的第一个索引

返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。

  • searchElement 要查找的元素。
  • fromIndex 可选 开始查找的位置。
    • 如果该索引值大于或等于数组长度,意味着不会在数组里查找,返回-1。
    • 如果参数中提供的索引值是一个负值,则将其作为数组末尾的一个抵消,即 -1 表示从最后一个元素开始查找,-2表示从倒数第二个元素开始查找 ,以此类推。
    • 如果抵消后的索引值仍小于0,则整个数组都将会被查询。

注意:如果参数中提供的索引值是一个负值,并不改变其查找顺序,查找顺序仍然是从前向后查询数组。

let colors = [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(colors.indexOf(5)) // 4
console.log(colors.indexOf(5, 10)) // -1 大于等于数组长度,返回 -1 
console.log(colors.indexOf(5, -1)) // -1 从倒数第 1 个查找,也就是从 9 向后查找 ,返回 -1
console.log(colors.indexOf(5, -6)) // 4 从倒数第 6 个查找,也就是从 4 向后查找,返回 4
console.log(colors.indexOf(5, -10)) // 4 抵消后索引值小于 0 ,查找整个数组,返回 4

lastIndexOf()返回指定元素的最后一个索引

返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,如果不存在则返回 -1。从数组的后面向前查找,从 fromIndex 处开始。

  • searchElement 被查找的元素。
  • fromIndex 可选 从此位置开始逆向查找。默认为数组的长度减 1(arr.length - 1),即整个数组都被查找。
    • 如果该值大于或等于数组的长度,则整个数组会被查找。
    • 如果为负值,将其视为从数组末尾向前的偏移。即使该值为负,数组仍然会被从后向前查找。
    • 如果为负值,其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。
let colors = [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(colors.lastIndexOf(1)) // 0 默认情况,整个数组都被查找
console.log(colors.lastIndexOf(9, 9)) // 8 fromIndex 大于等于数组长度,整个数组都被查找
console.log(colors.lastIndexOf(5, -5)) // 4 fromIndex 为负值,从数组末尾向前的偏移,这里就是从 5 开始查找,所以能找到 
console.log(colors.lastIndexOf(5, -6)) // -1 这里从 4 开始查找,因为从后向前查找,所以返回 -1
console.log(colors.lastIndexOf(5, -10)) // -1 fromIndex 绝对值大于数组长度,则方法返回 -1

includes()数组是否包含指定值(ES7)

用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

  • valueToFind 需要查找的元素值。
  • fromIndex 可选fromIndex 索引处开始查找 valueToFind
    • 如果为负值,则按升序从 array.length + fromIndex 的索引开始搜。

Note: 使用 includes()比较字符串和字符时是区分大小写。

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(numbers.includes(5)) // true
console.log(numbers.includes(5, 6)) // false fromIndex 大于 valueToFind 索引值
console.log(numbers.includes(5, 10)) // false fromIndex 大于等于数组长度
console.log(numbers.includes(5, -5)) // true array.length + fromIndex 也就是从索引值 4 开始查找  
console.log(numbers.includes(8, -1)) // false 从索引值 8 开始查找
console.log(numbers.includes(5, -10)) // true 如果计算出的索引小于 0,则整个数组都会被搜索。

迭代方法

forEach()对数组的每个元素执行一次给定的函数

按升序为数组中含有效值的每一项执行一次 callback 函数,那些已删除(使用delete方法等情况)或者未初始化的项将被跳过(例如在稀疏数组上,不包括undefined)。

  • callback 为数组中每个元素执行的函数,该函数接收一至三个参数:
    • currentValue 数组中正在处理的当前元素。
    • index 可选 数组中正在处理的当前元素的索引。
    • array 可选 forEach() 方法正在操作的数组。
  • thisArg 可选 当执行回调函数 callback 时,用作 this 的值。
    • 如果 thisArg 参数有值,则每次 callback 函数被调用时,this 都会指向 thisArg 参数。
    • 如果省略了 thisArg 参数,或者其值为 nullundefinedthis 则指向全局对象。

注意:

  1. forEach() 遍历的范围在第一次调用 callback 前就会确定。

    调用 forEach 后添加到数组中的项不会被 callback 访问到。

    如果已经存在的值被改变,则传递给 callback 的值是 forEach() 遍历到他们那一刻的值。

    如果已访问的元素在迭代时被删除了(例如使用 shift()),之后元素将被跳过。

  2. forEach() 为每个数组元素执行一次 callback 函数;它总是返回 undefined 值(也就是没有返回值),并且不可链式调用

  3. forEach() 被调用时,不会直接改变调用它的对象,但是那个对象可能会被 callback 函数改变。

    • 值类型不会被改变,引用类型会被改变。
  4. 除了抛出异常以外,没有办法中止或跳出 forEach() 循环。

//不对未初始化的值进行任何操作(稀疏数组)
let nums = [1, 3, , 7]
let numCallbackRuns = 0
nums.forEach(function (element) {
    console.log(element)
    numCallbackRuns++
})
console.log('numCallbackRuns:', numCallbackRuns) // 1 3 7 numCallbackRuns: 3

//使用thisArgs
function Counter() {
    this.sum = 0
    this.count = 0
}
Counter.prototype.add = function (array) {
    array.forEach(function (entry) {
        this.sum += entry
        ++this.count
    }, this)
    //因为 thisArg 参数(this)传给了 forEach(),每次调用时,它都被传给 callback 函数,作为它的 this 值。
}

const obj = new Counter()
obj.add([2, 5, 9])
// 3 === (1 + 1 + 1)
obj.sum
// 16 === (2 + 5 + 9) 

//使用箭头函数表达式传入函数,thisArgs 参数会被忽略
let nums2 = [1, 2, 3]
nums2.forEach((v, i, arr) => {
    console.log(this) // window window window
})

//如果数组在迭代时被修改了,则其他元素会被跳过
let words = ['one', 'two', 'three', 'four']
words.forEach((word) => {
    console.log(word)
    if (word === 'two') {
        words.shift()
    }
}) // one two four

//没有返回值
let nums3 = [1, 2, 3, 4, 5]
let value = nums3.forEach((v, i, arr) => { })
console.log(value) // undefined

//不支持链式调用
[1, 2, 3, 4, 5].forEach(item => {
    console.log(item)
}).filter(item => {
    return item > 2 // Uncaught TypeError: Cannot read property 'filter' of undefined
})


//forEach() 被调用时,调用它的对象被 callback 函数改变
let nums4 = [1, 2, 3, 4, 5]
nums4.forEach((v, i, arr) => {
    arr[i] = v * 2
})
console.log(nums4) // [2, 4, 6, 8, 10] 改变

nums4.forEach(item => {
    item = item * 2
})
console.log(nums4) // [2, 4, 6, 8, 10] 没有改变

//除了抛出异常,没办法中止或跳出 forEach() 循环
let nums5 = [1, 2, 3, 4, 5]
// nums5.forEach((v, i, arr) => {
//     console.log(v)
//     if (i === 2) {
//         break // Uncaught SyntaxError: Illegal break statement
//     } 
// })

nums5.forEach((v, i, arr) => {
    console.log(v)
    if (v === 2) {
        console.log(666)
        return false // 1 2 666 3 4 5
    }
})

map()对数组每一项都执行传入的函数,返回新数组

给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值(包括 undefined)组合起来形成一个新数组。

callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。

  • callback 生成新数组元素的函数,使用三个参数:
    • currentValue callback 数组中正在处理的当前元素。
    • index可选 callback 数组中正在处理的当前元素的索引。
    • array可选 map 方法调用的数组。
  • thisArg可选 执行 callback 函数时值被用作this
  1. map生成新数组,当你不打算使用返回的新数组却使用map是违背设计初衷的,请使用forEach或者for-of替代。

    你不该使用map的情况:

    • 你不打算使用返回的新数组;
    • 你没有从回调函数中返回值。
  2. 如果thisArg参数提供给map,则会被用作回调函数的this值。否则undefined会被用作回调函数的this值。

  3. map不修改调用它的原数组本身(当然可以在callback执行时改变原数组)。

  4. map方法处理数组元素的范围是在callback方法第一次调用之前就确定了。

    调用map方法之后追加的数组元素不会被callback访问。

    如果存在的数组元素改变了,那么传给callback的值是map访问该元素时的值。

    map函数调用后但在访问该元素前,该元素被删除的话,则无法被访问到。

  5. 根据规范中定义的算法,如果被map调用的数组是离散的,新数组将也是离散的保持相同的索引为空。

//map 返回新数组 原数组不变(可以通过 callback 改变)
let nums = [1, 4, 9]
let roots = nums.map(Math.sqrt)
console.log(roots, nums) // [1, 2, 3] [1, 4, 9]

//返回值是 undefined 也会组合成新数组
let newArr = [1, 2, 3, 4, 5].map(item => {
    if (item > 3) {
        return item
    }
})
console.log(newArr) // [undefined, undefined, undefined, 4, 5]

//使用 map 重新格式化数组中的对象
let kvArray = [{ key: 1, value: 10 }, { key: 2, value: 20 }, { key: 3, value: 30 }]
let refmArray = kvArray.map(function (obj) {
    let rObj = {}
    rObj[obj.key] = obj.value
    return rObj
})
console.log(refmArray, kvArray) // [{1:10},{2:20},{3:30}] [{ key: 1, value: 10 }, { key: 2, value: 20 }, { key: 3, value: 30 }]

//使用一个包含一个参数的函数来mapping(构建)一个数字数组
let nums2 = [1, 4, 9]
let doubles = nums2.map(function (num) {
    return num * 2
})
console.log(doubles, nums2) // [2, 8, 18] [1, 4, 9]

//querySelectorAll 应用
let elems = document.querySelectorAll('select option:checked')
let values = Array.prototype.map.call(elems, function (obj) {
    return obj.value
})
console.log(values) // [] 我们获得了文档里所有选中的选项,并将其打印

技巧案例

console.log(['1', '2', '3'].map(parseInt)) // [1, NaN, NaN]

出现这个结果是因为parseInt接收两个参数:

  • 第一个参数是一个表达式,表示要被解析的值。
  • 第二个参数是一个数字,表示字符串的基数。

map的第三个参数原数组本身parseInt忽视,第二个参数传过来的索引值被当成进制数来使用,所以返回NaN

解决方案

//返回10进制整数
function returnInt(element) {
    return parseInt(element, 10)
}
console.log(['1', '2', '3'].map(returnInt)) // [1, 2, 3]

//使用箭头函数
console.log(['1', '2', '3'].map(str => parseInt(str))) // [1, 2, 3]

//使用Number转换字符串
console.log(['1', '2', '3'].map(Number)) // [1, 2, 3]

filter()过滤原始函数,返回新数组

为数组中的每个元素调用一次 callback 函数,并利用所有使得 callback 返回 true 或等价于 true 的值的元素创建一个新数组。

  • callback 用来测试数组的每个元素的函数。返回 true 表示该元素通过测试,保留该元素,false 则不保留。它接受以下三个参数:
    • element 数组中当前正在处理的元素。
    • index可选 正在处理的元素在数组中的索引。
    • array可选 调用了 filter 的数组本身。
  • thisArg可选 执行 callback 时,用于 this 的值。
  1. callback 只会在已经赋值的索引上被调用,对于那些已经被删除或者从未被赋值的索引不会被调用。

    那些没有通过 callback 测试的元素会被跳过,不会被包含在新数组中。

  2. 如果为 filter 提供一个 thisArg 参数,则它会被作为 callback 被调用时的 this 值。

    否则,callbackthis 值在非严格模式下将是全局对象,严格模式下为 undefined

  3. filter 不会改变原数组,它返回过滤后的新数组。

  4. filter 遍历的元素范围在第一次调用 callback 之前就已经确定了。

    在调用 filter 之后被添加到数组中的元素不会被 filter 遍历到。

    如果已经存在的元素被改变了,则他们传入 callback 的值是 filter 遍历到它们那一刻的值。

    被删除或从来未被赋值的元素不会被遍历到。

const words = ['HTML', 'CSS', 'JavaScript', 'Java', 'Vue', 'React']
const result = words.filter(word => word.length > 6)
console.log(result) // ["JavaScript"]

some()数组中是否有满足条件的元素

数组中的每一个元素执行一次 callback 函数,直到找到一个使得 callback 返回一个“真值”(即可转换为布尔值 true 的值)。

如果找到了这样一个值,some() 将会立即返回 true。否则,some() 返回 false

callback 只会在那些“有值”的索引上被调用,不会在那些被删除或从来未被赋值的索引上调用。

  • callback 用来测试每个元素的函数,接受三个参数:
    • element 数组中正在处理的元素。
    • index 可选 数组中正在处理的元素的索引值。
    • array可选 some()被调用的数组。
  • thisArg可选 执行 callback 时使用的 this 值。
  1. 如果一个thisArg参数提供给some(),它将被用作调用的 callbackthis 值。否则, 它的 this value 将是 undefined

  2. some() 被调用时不会改变数组。

  3. some() 遍历的元素的范围在第一次调用 callback 前就已经确定了。

    在调用 some() 后被添加到数组中的值不会被 callback 访问到。

    如果数组中存在且还未被访问到的元素被 callback 改变了,则其传递给 callback 的值是 some() 访问到它那一刻的值。

    已经被删除的元素不会被访问到。

//测试数组元素的值
function isBiggerThan10(element, index, array) {
    return element > 10
}

console.log([2, 5, 8, 9, 1].some(isBiggerThan10)) // false
console.log([12, 5, 8, 9, 1].some(isBiggerThan10)) // true

//使用箭头函数测试数组元素的值
console.log([1, 5, 6, 9].some(x => x > 10)) // false
console.log([12, 5, 1, 9].some(x => x > 10)) // true

//判断数组元素中是否存在某个值
let fruits = ['apple', 'banana', 'mango', 'guava']

function checkAvailability(arr, val) {
    return arr.some(function (arrVal) {
        return val === arrVal
    })
}
console.log(checkAvailability(fruits, 'kela')) // false
console.log(checkAvailability(fruits, 'banana')) // true

//将任意值转换为布尔类型
let TRUTHY_VALUES = [true, 'true', 1]
function getBoolean(value) {
    'use strict'
    if (typeof value === 'string') {
        value = value.toLowerCase().trim()
    }
    return TRUTHY_VALUES.some(function (t) {
        return t === value
    })
}

console.log(getBoolean(false)) // false
console.log(getBoolean('false')) // false
console.log(getBoolean(1)) // true
console.log(getBoolean('true')) // true

every()数组中所有元素是否满足条件

为数组中的每个元素执行一次 callback 函数,直到它找到一个会使 callback 返回 falsy 的元素。

如果发现了一个这样的元素,every 方法将会立即返回 false。否则,callback 为每一个元素返回 trueevery 就会返回 true

callback 只会为那些已经被赋值的索引调用。不会为那些被删除或从未被赋值的索引调用。

  • callback 用来测试每个元素的函数,它可以接收三个参数:
    • element 用于测试的当前值。
    • index可选 用于测试的当前值的索引。
    • array可选 调用 every 的当前数组。
  • thisArg 执行 callback 时使用的 this 值。
  1. 如果为 every 提供一个 thisArg 参数,则该参数为调用 callback 时的 this 值。

    如果省略该参数,则 callback 被调用时的 this 值,在非严格模式下为全局对象,在严格模式下传入 undefined

  2. every 不会改变原数组。

  3. every 遍历的元素范围在第一次调用 callback 之前就已确定了。

    在调用 every 之后添加到数组中的元素不会被 callback 访问到。

    如果数组中存在的元素被更改,则他们传入 callback 的值是 every 访问到他们那一刻的值。

    那些被删除的元素或从来未被赋值的元素将不会被访问到。

  4. every 和数学中的"所有"类似,当所有的元素都符合条件才会返回true。正因如此,若传入一个空数组,无论如何都会返回 true

//检测所有数组元素的大小
function isBigEnough(element, index, array) {
    return element >= 10
}

console.log([12, 5, 8, 130, 44].every(isBigEnough)) // false
console.log([12, 54, 18, 130, 44].every(isBigEnough)) // true

//箭头函数
console.log([12, 5, 8, 130, 44].every(x => x >= 10)) // false
console.log([12, 54, 18, 130, 44].every(x => x >= 10)) // true

reduce()为数组提供一个累加器,汇总为一个值

为数组中的每一个元素依次执行callback函数,不包括数组中被删除或从未被赋值的元素。

  • callback 执行数组中每个值 (如果没有提供 initialValue则第一个值除外)的函数,包含四个参数:

    • accumulator 累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue(见于下方)。
    • currentValue 数组中正在处理的元素。
    • index 可选 数组中正在处理的当前元素的索引。 如果提供了initialValue,则起始索引号为0,否则从索引1起始。
    • array可选 调用reduce()的数组。
  • initialValue可选 作为第一次调用 callback函数时的第一个参数的值。

    如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。

  1. 回调函数第一次执行时,accumulatorcurrentValue的取值有两种情况:

    如果调用reduce()时提供了initialValueaccumulator取值为initialValuecurrentValue取数组中的第一个值;

    如果没有提供 initialValue,那么accumulator取数组中的第一个值,currentValue取数组中的第二个值。

  2. 如果数组为空且没有提供initialValue,会抛出TypeError

    如果数组仅有一个元素(无论位置如何)并且没有提供initialValue, 或者有提供initialValue但是数组为空,那么此唯一值将被返回并且callback不会被执行。

注意: 如果没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。如果提供initialValue,从索引0开始。

提供初始值通常更安全,正如下面的例子,如果没有提供initialValue,则可能有四种输出:

let maxCallback = (acc, cur) => Math.max(acc.x, cur.x)
let maxCallback2 = (max, cur) => Math.max(max, cur)

// reduce()没有初始值
console.log([{ x: 2 }, { x: 22 }, { x: 42 }].reduce(maxCallback)) // NaN 
console.log([{ x: 2 }, { x: 22 }].reduce(maxCallback))  // 22 没提供 initialValue,accumulator 取 {x:2},currentValue 取数组中的第二个值 {x:22}
console.log([{ x: 2 }].reduce(maxCallback)) // {x: 2} 数组只用一个元素,此唯一值将被返回并且 callback 不会被执行
console.log([].reduce(maxCallback)) //Uncaught TypeError: Reduce of empty array with no initial value 数组为空且没有提供 initialValue 

// map/reduce 这是更好的方案,既是传入空数组或更大数组也可正常执行
console.log([{ x: 22 }, { x: 42 }].map(el => el.x).reduce(maxCallback2, -Infinity)) // 42

第一个为什么输出 NaN 呢?

  1. 没提供 initialValueaccumulator{x:2}currentValue 取数组中的第二个值 {x:22}

    就相当于[{ x: 22 }, { x: 42 }].reduce(maxCallback, { x: 2 })

  2. 第一次运算是 { x:2 }{ x:22 } 结果是 22。

  3. 第二次运算是 22 和 { x:42 } ,(22).x 是 undefined,那么 Math.max(undefined,42)结果是NaN

所以先试用 map 把 x 都取出来是更好的方案。

reduce() 如何运行

假如运行下段reduce()代码:

[0, 1, 2, 3, 4].reduce(function (accumulator, currentValue, currentIndex, array) {
    return accumulator + currentValue
})

//使用箭头函数
[0, 1, 2, 3, 4].reduce((prev, curr) => prev + curr)

callback 被调用四次,每次调用的参数和返回值如下表:

callbackaccumulatorcurrentValuecurrentIndexarrayreturn value
first call011[0, 1, 2, 3, 4]1
second call122[0, 1, 2, 3, 4]3
third call333[0, 1, 2, 3, 4]6
fourth call644[0, 1, 2, 3, 4]10

reduce返回的值将是最后一次回调返回值(10)。

如果你打算提供一个初始值作为reduce()方法的第二个参数,以下是运行过程及结果:

[0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => {
    return accumulator + currentValue
}, 10)
callbackaccumulatorcurrentValuecurrentIndexarrayreturn value
first call1000[0, 1, 2, 3, 4]10
second call1011[0, 1, 2, 3, 4]11
third call1122[0, 1, 2, 3, 4]13
fourth call1333[0, 1, 2, 3, 4]16
fifth call1644[0, 1, 2, 3, 4]20

这种情况下reduce()返回的值是20

//数组里所有值的和
let sum = [0, 1, 2, 3].reduce(function (accumulator, currentValue) {
    return accumulator + currentValue
}, 0)
console.log(sum) // 6

//将二维数组转化为一维
let flattened = [[0, 1], [2, 3], [4, 5]].reduce(
    function (a, b) {
        return a.concat(b)
    }, []
)
console.log(flattened) // [0, 1, 2, 3, 4, 5]

//计算数组中每个元素出现的次数
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice']
let countedNames = names.reduce(function (allNames, name) {
    if (name in allNames) {
        allNames[name]++
    } else {
        allNames[name] = 1
    }
    return allNames
}, {})
console.log(countedNames) // {Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}

//按属性对object分类
var people = [
    { name: 'Alice', age: 21 },
    { name: 'Max', age: 20 },
    { name: 'Jane', age: 20 }
]

function groupBy(objectArray, property) {
    return objectArray.reduce(function (acc, obj) {
        let key = obj[property]
        if (!acc[key]) {
            acc[key] = []
        }
        acc[key].push(obj)
        return acc
    }, {})
}

let groupedPeople = groupBy(people, 'age')
console.log(groupedPeople)
// {
//   20: [
//     { name: 'Max', age: 20 },
//     { name: 'Jane', age: 20 }
//   ],
//   21: [{ name: 'Alice', age: 21 }]
// }

reduceRight()为数组提供一个累加器,将其减少为单个值(从右到左)

为数组中每个元素调用一次 callback 回调函数,但是数组中被删除的索引或从未被赋值的索引会跳过。

  • callback 一个回调函数,用于操作数组中的每个元素,它可接受四个参数:

    • accumulator 累加器:上一次调用回调函数时,回调函数返回的值。

      首次调用回调函数时,如果 initialValue 存在,累加器即为 initialValue,否则须为数组中的最后一个元素。

    • currentValue 当前元素:当前被处理的元素。

    • index可选 数组中当前被处理的元素的索引。

    • array可选 调用 reduceRight() 的数组。

  • initialValue可选 首次调用 callback 函数时,累加器 accumulator 的值。

    如果未提供该初始值,则将使用数组中的最后一个元素,并跳过该元素。

    如果不给出初始值,则需保证数组不为空。 否则,在空数组上调用 reducereduceRight 且未提供初始值(例如 [].reduce( (acc, cur, idx, arr) => {} ) )的话,会导致类型错误 TypeError: reduce of empty array with no initial value

可以像下面这样调用 reduceRight 的回调函数 callback

array.reduceRight(function (accumulator, currentValue, index, array) {
    // ...
})

首次调用回调函数时,accumulatorcurrentValue 的可能取值情况有两种:

  • 如果在调用 reduceRight 时提供了 initialValue 参数,则 accumulator 等于 initialValuecurrentValue 等于数组中的最后一个元素。
  • 如果没有提供 initialValue 参数,则 accumulator 等于数组最后一个元素, currentValue 等于数组中倒数第二个元素。

如果数组为空,但提供了 initialValue 参数,或如果数组中只有一个元素,且没有提供 initialValue 参数,将会直接返回 initialValue 参数或数组中的那一个元素。这两种情况下,都不会调用 callback 函数。

最终,首次调用时的情况可汇总为此表:

数组内元素数量是否提供 initialValue结果
> 1未提供accumulator 为数组中(下略)最后一个元素 currentValue 为倒数第二个元素
提供accumulatorinitialValue currentValue 为最后一个元素
= 1未提供直接返回数组中的唯一一个元素
= 0提供直接返回 initialValue
未提供抛出 TypeError 错误

该函数的完整执行过程见下例:

[0, 1, 2, 3, 4].reduceRight(function (previousValue, currentValue, index, array) {
    return previousValue + currentValue
})

一共会调用四次回调函数,每次调用的参数及返回值如下:

callbackpreviousValuecurrentValueindexarray返回值
第一次调用433[0,1,2,3,4]7
第二次调用722[0,1,2,3,4]9
第三次调用911[0,1,2,3,4]10
第四次调用1000[0,1,2,3,4]10

reduceRight 返回值是最后一次调用回调的返回值(10)。

如果提供了一个 initialValue 参数,则结果如下:

[0, 1, 2, 3, 4].reduceRight(function (previousValue, currentValue, index, array) {
    return previousValue + currentValue
}, 10)
callbackpreviousValuecurrentValueindexarray返回值
第一次调用1044[0,1,2,3,4]14
第二次调用1433[0,1,2,3,4]17
第三次调用1722[0,1,2,3,4]19
第四次调用1911[0,1,2,3,4]20
第五次调用2000[0,1,2,3,4]20

这时,reduceRight 返回值为 20。

//求一个数组中所有值的和
let sum = [0, 1, 2, 3].reduceRight(function (a, b) {
    return a + b
})
console.log(sum) // 6

//扁平化(flatten)一个二维数组
let flattened = [[0, 1], [2, 3], [4, 5]].reduceRight(function (a, b) {
    return a.concat(b)
}, [])
console.log(flattened) // [4, 5, 2, 3, 0, 1]

//展示 reduce 与 reduceRight 之间的区别
let a = ['1', '2', '3', '4', '5']
let left = a.reduce(function (prev, cur) {
    return prev + cur
})

let right = a.reduceRight(function (prev, cur) {
    return prev + cur
})

console.log(left) // 12345
console.log(right) // 54321

find()&findIndex()(ES6)

find方法对数组中的每一项元素执行一次 callback 函数,直至有一个 callback 返回 true

当找到了这样一个元素后,该方法会立即返回这个元素的值,否则返回 undefined

注意:callback 函数会为数组中的每个索引调用即从 0 length - 1,而不仅仅是那些被赋值的索引,这意味着对于稀疏数组来说,该方法的效率要低于那些只遍历有值的索引的方法。

  • callback 在数组每一项上执行的函数,接收 3 个参数:
    • element 当前遍历到的元素。
    • index可选 当前遍历到的索引。
    • array可选 数组本身。
  • thisArg可选 执行回调时用作this 的对象。
  1. 如果提供了 thisArg参数,那么它将作为每次 callback函数执行时的this ,如果未提供,则使用 undefined

  2. find方法不会改变数组。

  3. 在第一次调用 callback函数时会确定元素的索引范围,因此在 find方法开始执行之后添加到数组的新元素将不会被 callback函数访问到。

    如果数组中一个尚未被callback函数访问到的元素的值被callback函数所改变,那么当callback函数访问到它时,它的值是将是根据它在数组中的索引所访问到的当前值。

    被删除的元素仍旧会被访问到,但是其值已经是 undefined 了。

//用对象的属性查找数组里的对象
let inventory = [
    { name: 'apples', quantity: 2 },
    { name: 'bananas', quantity: 0 },
    { name: 'cherries', quantity: 5 }
]

function findCherries(fruit) {
    return fruit.name === 'cherries'
}

console.log(inventory.find(findCherries)) // {name: "cherries", quantity: 5}

//寻找数组中的质数
function isPrime(element, index, array) {
    let start = 2
    while (start <= Math.sqrt(element)) {
        if (element % start++ < 1) {
            return false
        }
    }
    return element > 1
}
console.log([4, 6, 8, 12].find(isPrime)) // undefined 找不到质数返回 false
console.log([4, 5, 8, 12].find(isPrime)) // 5

findIndex方法对数组中的每个数组索引0..length-1(包括)执行一次callback函数,直到找到一个callback函数返回真实值(强制为true)的值。

如果找到这样的元素,findIndex会立即返回该元素的索引。如果回调从不返回真值,或者数组的length为0,则findIndex返回-1。

与某些其他数组方法(如Array#some)不同,在稀疏数组中,即使对于数组中不存在的条目的索引也会调用回调函数。

  • callback 针对数组中的每个元素, 都会执行该回调函数, 执行时会自动传入下面三个参数:
    • element 当前元素。
    • index 当前元素的索引。
    • array 调用findIndex的数组。
  • thisArg 可选。执行callback时作为this对象的值。
  1. 如果一个 thisArg 参数被提供给 findIndex, 它将会被当作this使用在每次回调函数被调用的时候。如果没有被提供,将会使用undefined

  2. findIndex不会修改所调用的数组。

  3. 在第一次调用callback函数时会确定元素的索引范围,因此在findIndex方法开始执行之后添加到数组的新元素将不会被callback函数访问到。

    如果数组中一个尚未被callback函数访问到的元素的值被callback函数所改变,那么当callback函数访问到它时,它的值是将是根据它在数组中的索引所访问到的当前值。

    被删除的元素仍然会被访问到。

//查找数组中首个质数元素的索引
function isPrime(element, index, array) {
    let start = 2
    while (start <= Math.sqrt(element)) {
        if (element % start++ < 1) {
            return false
        }
    }
    return element > 1
}

console.log([4, 6, 8, 12].findIndex(isPrime)) // -1
console.log([4, 6, 7, 12].findIndex(isPrime)) // 2

keys()&values()&entries()(ES6)

keys() 方法返回一个包含数组中每个索引键的Array Iterator对象。

const arr = ['a', 'b', 'c']
const iterator = arr.keys()

for (const key of iterator) {
    console.log(key)
}
// 0
// 1
// 2

let arr2 = ['a', 'c']
let sparseKeys = Object.keys(arr)
let denseKeys = [...arr.keys()]
console.log(sparseKeys) // ["0", "1", "2"]
console.log(denseKeys) // [0, 1, 2] 索引迭代器会包含那些没有对应元素的索引

values() 方法返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值。

const arr = ['a', 'b', 'c']
const iterator = arr.values()

for (const value of iterator) {
    console.log(value)
}
// a
// b
// c

//使用 for...of 循环进行迭代
let arr2 = ['w', 'y', 'k', 'o', 'p']
let eArr = arr2.values()

for (let letter of eArr) {
    console.log(letter)
}
//w
//y
//k
//o
//p

//使用 .next() 迭代
let arr4 = ['a', 'b', 'c', 'd', 'e']
let iterator = arr4.values()
console.log(iterator.next()) // {value: "a", done: false}
console.log(iterator.next().value) // b
console.log(iterator.next()['value']) // c
console.log(iterator.next()) // {value: "d", done: false}
console.log(iterator.next()) // {value: "e", done: false}
console.log(iterator.next()) // {value: undefined, done: true}
console.log(iterator.next().value) // undefined

//如果数组中元素改变,那么迭代器的值也会改变
let arr5 = ['a', 'b', 'c', 'd', 'e']
let iterator = arr5.values()
console.log(iterator) // Array Iterator {  }
iterator.next().value // "a"
arr5[1] = 'n'
iterator.next().value //  "n"

entries() 方法返回一个新的 Array Iterator 对象,该对象包含数组中每个索引的键/值对。

// Array Iterator
let arr = ['a', 'b', 'c']
let iterator = arr.entries()
console.log(iterator) // Array Iterator {}

// iterator.next()
console.log(iterator.next()) // {value: [0, "a"], done: false}

// iterator.next方法运行
let a = []

for (let i = 0; i < arr.length; i++) {
    let tem = iterator.next() // 每次迭代时更新next
    console.log(tem.done) // 这里可以看到更新后的done都是false
    if (tem.done !== true) { // 遍历迭代器结束done才是true
        console.log(tem.value)
        a[i] = tem.value
    }
}
// false
// [1, "b"]
// false
// [2, "c"]
// true
console.log(a) // [[1, "b"],[2, "c"]]

参考:

JavaScript 高级程序设计 第4版

Array MDN

JavaScript 中 forEach、map、filter 详细

forEach、map、filter、find、sort、some等易错点整理