JavaScript数组
目录
1. 基本概念
2. 创建数组对象
2.1 字面量创建
2.2 构造函数创建
2.3 静态方法Array.of()
2.4 静态方法Array.from()
2.5 空槽位
3. 常用实例方法
3.1 修改方法
3.11 push 和 unshift
3.12 shift 和 pop
3.13 fill
3.14 cpoyWithin
3.15 reverse
3.16 sort
3.17 splice
3.2 非修改方法【1】
3.21 forEach
3.22 map
3.23 filter
3.24 find / findLast
3.25 findIndex / findLastIndex
3.26 some
3.27 every
3.3 非修改方法【2】
3.31 indexOf / lastIndexOf
3.32 reduce / reduceRight
3.33 slice
3.34 flat
3.35 includes
3.36 toSpliced
3.37 join
3.38 contact
4. 其他方法
4.1 Array.isArray
4.2 Math对象方法
1. 基本概念
在JavaScript中,数组(Array)是一种特殊的对象类型,它提供了一种高效的方式来组织和管理数据元素。
JS 数组是可以动态调整大小的,并且可以包含任何数据类型的,如果不需要这个特征,可使用类型化数组。
JS数组不是关联数组,不能使用任意字符串作为索引访问数组元素,必须使用非负整数(或它们各自的字符串形式)作为索引访问。
2. 创建数组对象
JS提供了多种数组创建的方式
2.1 字面量创建
const arr = [1, 2, 3, 'four', true]
2.2 构造函数创建
const arr = new Array(1, 2, 3, 'four', true)
//也可以指定数组的长度(但这种方法不初始化数组元素)
const arr = new Array(5) // 创建一个长度为5的空数组
2.3 静态方法Array.of()
这种方法与构造函数 new Array() 创建数组一样,但如果只传入单个元素,new Array()会将其解释为数组长度,所以只能是非负整数,创建出来的数组也是空数组。而 Array.of()会将其解释为元素内容,即创建一个只有一个元素的数组。总的来说,这与数组字面量相似,但提供了一种函数式的方式来创建数组。
const arr = Array.of(-6)
console.log(arr) //[-6]
2.4 静态方法Array.from()
// 创建一个长度为5的数组,每个元素是其索引的两倍:
const arr = Array.from({ length: 5 }, (v, i) => i * 2)
console.log(arr) //[0, 2, 4, 6, 8]
它常用于将将类数组或可迭代对象(如 Map、Set、String、arguments 、NodeList 等)转换为数组,但这种方式是浅拷贝创建新数组。
Array.from(arrayLike, mapFn, thisArg)
arrayLike:类数组或可迭代对象
mapFn(可选):新数组中的每个元素会执行该回调函数
thisArg(可选):执行 mapFn 函数时 this 的值
----------------------------------------------
处理arguments伪数组:
function foo() { let args = Array.from(arguments); console.log(args);
} foo(1, 2, 3); // 输出: [1, 2, 3]----------------------------------------------
字符串转数组:
let str = 'hello';
let strArray = Array.from(str);
console.log(strArray); // ['h', 'e', 'l', 'l', 'o']
---------------------------------------------
Set 转换为数组
let set = new Set([1, 2, 3, 4, 5]);
let arrayFromSet = Array.from(set); console.log(arrayFromSet); // [1, 2, 3, 4, 5]
2.5 空槽位
在JavaScript中,数组的空槽位(也称为“空洞”或“稀疏数组”)是指数组中那些存在但未被赋值的索引位置。这些空槽位在数组的length属性中被计算在内,但实际上并不包含任何值。
那么空槽位主要是如何产生的呢?
JS数组是动态的,同时它的长度与length属性数值是关联的,这意味着,我们可以通过显示的增加或者减少length属性值来扩展或缩减数组:
const arr = [1, 2, 3, 4]
//数组长度,元素个数
console.log(arr.length) //4
//减少length
arr.length -= 3
//删除了数组末尾的3个元素
console.log(arr) //[1]
//增加 length
arr.length += 3
console.log(arr) //[1,空*3]
可以看见,当我们增加length属性值的时候,数组会使用空槽来扩展数组,而不是创建任何新元素—— 甚至不会是 undefined。与之相同的:
使用构造函数传入一个非负整数作为数组长度创建数组时,通过空槽位扩展数组
coconst arr = new Array(5)
console.log(arr)//[空 x5]直接赋值时,也可能产生空槽位
let arr = []
arr[2] = 'three' // 索引0和1现在是空槽位
console.log(arr)//[空 x2,'three']字面量创建,
let array = [1, 2, 3, 4, , , , , 8]
console.log(array.reverse()) //['8', 空 ×4, 4, 3, 2, 1]
不过,虽然没有创建为undefined数据,但在JS中未被操作或赋值的变量本就代表着undefined
const arr = new Array(10)
console.log(arr[9] === undefined) //true
那么,既然其可以等价于undefined,又为何需要专门提及呢?
因为,不同的数组方法在遇到空槽时可能有不同的行为
通常,较旧的方法(例如 forEach)处理空槽的方式与处理包含 undefined 索引的方式不同。
const arr = [1, undefined, undefined]
arr[5] = 5
console.log(arr) // [1, undefined, undefined, 空 ×2, 5]//forEach并不会把空槽位当作undefined处理,甚至根本不会访问空槽
arr.forEach((e) => console.log(e))
//输出:1 undefined undefined 5
对空槽进行特殊处理的方法包括:concat()、copyWithin()、every()、filter()、flat()、flatMap()、forEach()、indexOf()、lastIndexOf()、map()、reduce()、reduceRight()、reverse()、slice()、some()、sort() 和 splice()。诸如 forEach 之类的迭代方法根本不会访问空槽。其他方法,如 concat、copyWithin 等,在进行复制时会保留空槽,因此最终数组依然是稀疏的。
const arr = [undefined]
arr[3] = 3
console.log(arr) //[undefined, 空 ×2, 3]
console.log(arr.concat(arr)) // [undefined, 空 ×2, 3, undefined, 空 ×2, 3]
console.log(arr.reverse()) //[3, 空 ×2, undefined]
较新的方法(例如 keys)不会对空槽进行特殊处理,而是将它们视为包含 undefined。将空槽合并为 undefined 元素方法有:entries()、fill()、find()、findIndex()、findLast()、findLastIndex()、includes()、join()、keys()、toLocaleString()、values() 和 with(),以及[....array]扩展运算符
const arr = []
arr[3] = 3
console.log(arr) //[ 空 ×3, 3]
console.log(arr.findIndex((e) => e === undefined)) // 0
console.log([...arr]) //[undefined, undefined, undefined, 3]
3. 常用方法
与其它标准内置对象一样,Array也提供了许多方便好用的属性方法来操作其实例,它们主要定义在Array.prototype上。
3.1 修改方法
这些方法会对原数组进行修改
3.11 push 和 unshift
push,向数组末尾添加一个或多个元素,并返回新的数组长度
unshift,向数组开头添加一个或多个元素,并返回新的数组长度
const array = [1, 2, 3]
console.log(array.push(777)) // 4
console.log(array) // [1, 2, 3, 777]
console.log(array.unshift(666)) // 5
console.log(array) // [666, 1, 2, 3, 777]
3.12 shift 和 pop
pop,删除并返回数组的最后一个元素,如果数组为空,则返回undefined
。
shift,删除并返回数组的第一个元素,如果数组为空,则返回undefined
。
const array = [666, 2, 3, 4, 777]
console.log(array.pop()) // 777
console.log(array) // [666, 1, 2, 3]
console.log(array.shift()) // 666
console.log(array) // [2, 3, 4]
3.13 fill
fill
,将数组中的元素替换为指定的值,可以指定索引范围,如果传入索引,那么将会默认替换全部元素。同时,fill会访问空槽位,将其视为undefined,所以也可以替换它
const array = new Array(5).fill('null', 2, 4)
console.log(array) //[空 ×2, 'null', 'null', 空]
3.14 cpoyWithin
cpoyWithin,浅拷贝数组的一部分到同一数组中的另一个位置,并返回它,会修改原数组的内容,但不会改变原数组的长度。
copyWithin(target, start, end)
参数 | 描述 | 选值 |
target | 序列开始替换的目标位置 | target < 0,则使用 target + array.length |
target < -array.length,则使用 0 | ||
target >= array.length,则不会拷贝任何内容 | ||
target > start ,则复制只会持续到 array.length 结束,永远不会扩展数组 | ||
start | 可选,要复制的元素序列的起始位置 | start < 0,则使用 start + array.length |
如果省略 start 或 start < -array.length,则默认为 0 | ||
如果 start >= array.length,则不会拷贝任何内容 | ||
end | 可选,要复制的元素序列的结束位置,但不包括 end 这个位置的元素 | end < 0,则实际是 end + array.length |
end < -array.length,则使用0 | ||
如果省略 end 或 end >= array.length,则默认为 array.length,这将导致直到数组末尾的所有元素都被复制 | ||
如果 end 位于 start 之前,则不会拷贝任何内容 |
let array = [0, 1, 2, 3, 4, 5]
console.log(array) //[0, 1, 2, 3, 4, 5]
// 将索引3-4(不包括4)之间的元素,从索引0开始依次复制
console.log(array.copyWithin(0, 3, 4)) //[3, 1, 2, 3, 4, 5]
copyWithin不会视空槽位为undefined,但会访问复制它
const array = new Array(5).fill('null', 2, 3)
console.log(array) //[空 ×2, 'null', 空 ×2]
console.log(array.copyWithin(2, 4)) //[ 空 × 5]
copyWithin() 方法的工作原理类似于 C 和 C++ 的 memmove,是一种移动数组数据的高性能方法。序列在一次操作中被复制和粘贴;即使复制和粘贴区域重叠,粘贴的序列也将具有复制值。
3.15 reverse
reverse,就地反转数组中的元素顺序并返回它。与copyWithin一样,会访问空槽位并复制保留它
let array = [1, 2, 3, 4, , , , , 8]
console.log(array.reverse()) //['8', 空 ×4, 4, 3, 2, 1]
3.16 sort
sort,就地排序数组并返回它。默认情况下,会将数组元素(即使是数字)转换为字符串,然后按照字符串的Unicode码点顺序进行排序。这可能会导致非直观的排序结果
const arr = ['a', 3, '11', 'ab', '2', 1, '15']
arr.sort()
console.log(arr) // 输出:[1, '11', '15', '2', 3, 'a', 'ab']
对于空槽位和undefined,sort会先将它移动到数组的末尾,再进行排序。不过undefined会被放在空槽位之前。
console.log(['a', , , 'b'].sort()) // ['a', 'b', 空x 2]
console.log([, undefined, 'a', 'b'].sort()) // ["a", "b", undefined, 空]
我们也可以使用一个比较函数做参数,来改变默认排序。比较函数接受两个参数,而sort则根据比较的返回值来确定排序顺序。比如:
比较函数接收a,b两个参数,当比较函数的返回值 < 0时,a排在b之前;返回值 > 0,b排在a之前。 返回值 === 0时,a和b的相对位置不变。由此可实现数组的升序和降序排列
const numbers = [40, 100, 1, 5, 25]
numbers.sort((a, b) => b - a)
console.log(numbers) // 输出: [100, 40, 25, 5, 1]
也可以设置复杂的比较函数,注意,在比较函数,是访问不到空槽位和undefined的
const arr = [, , 1, 2, '3', undefined, null]
arr.sort((a, b) => {if (typeof a == typeof b) return 1 //升序else if (a == null || b == null) return 0else return -1 //降序
})
console.log(arr) // ['3', 1, 2, null, undefined, 空 ×2]
3.17 splice
splice,就地移除或者替换已存在的元素和/或添加新的元素。会返回一个包含被删除的元素的新数组,如果没有删除元素,则返回一个空数组。
会保留数组的稀疏性,即同 copyWithin,会访问并复制空槽位
splice(start, deleteCount, item1, item2, /* …, */ itemN)
start,从该索引处开始删除元素
deleteCount,删除元素的数量(如果省略,则默认start开始删删完)
item1, item2, /* …, */ itemN:删除后,从start开始插入的元素
-------------------------------------------------------------// 删除
let testArray = [1, 2, 3, 4, 5, 6]
console.log(testArray.splice(2, 3)) //[3, 4, 5] 从2开始删除3个元素
console.log(testArray) // [1, 2, 6]// 插入
let testArray = [1, 2, 3, 4, 5, 6]
console.log(testArray.splice(2, 0, 'a', 'b')) //[] 从2开始删除0个,插入'a', 'b'
console.log(testArray) // [1, 2, 'a', 'b', 3, 4, 5, 6]// 替换
let testArray = [1, 2, 3, 4, 5, 6]
console.log(testArray.splice(2, 1, 'a', 'b')) //[3] 从2开始删除1个,插入'a', 'b'
console.log(testArray) // [1, 2, 'a', 'b', 4, 5, 6]
3.2 非修改方法【1】
1. 这些方法不会对原数组进行修改,
2. 这些方法接收一个回调函数作为参数,且回调函数可接受三个参数,分别是当前访问元素,当前访问元素索引,访问的数组。
3. 这些方法可以使用函数提供的改变this指向的方法来遍历非数组对象中,属性为整数的属性值。
注意这些非数组对象需要有length属性
3.21 forEach
forEach,遍历数组,并对数组的每个元素执行一次给定的函数。
不访问空槽位,无返回值
arr.forEach((item, index, array) => {console.log(item) //item元素当亲元素console.log(index) //index数组索引console.log(array) //array数组本身
})
//通常只用于遍历访问元素
const array = [1, 2, , , , 3, 4]
array.forEach((e) => console.log(e))//1 2 3 4//在非数组对象上使用forEach
const obj = {length: 2,0: '6',1: '6',2: '6',3: '6'
}
// forEach() 方法读取 this 的 length 属性,然后访问每个整数索引。
Array.prototype.forEach.call(obj, (e) => console.log(e)) //6 6
3.22 map
map,创建一个由原数组中的每个元素都调用一次提供的函数后的返回值组成的新数组,并返回。
不访问空槽位,但空槽的索引在返回的数组中仍然为空
const array = [1, 2, , , , 3, 4]
const doubled = array.map((e) => e * 2)
console.log(doubled) // [2, 4, 空 ×3, 6, 8]//因为不会访问空槽位,所以使用map初始化,空槽数组时通常需要借助fill
new Array(n).fill(0).map(()=>{})
由于其回调函数会接受三个参数(元素,索引,数组),由此有一个值得注意的问题,当map与parseInt一起使用时:
const array = ['1', '2', '3', '4']
console.log(array.map(parseInt)) //[1, NaN, NaN, NaN]
console.log(array.map((e) => parseInt(e))) //[1, 2, 3, 4]
当我们直接用parseInt等函数作map的回调函数时,map会将元素,索引,数组等三个参数一起传入回调函数parseInt,而恰好parseInt可以接收两个参数(参数是表达式,解析该表达式的基数)。所以就变成了以index为基数来解析element,并返回解析结果......其结果当然不是一个数字
这一点对于forEach,filter和find,findIndex等方法与其他类似函数结合使用时,一样值得注意
3.23 filter
filter,创建并返回给定数组一部分的浅拷贝,其包含通过所提供函数实现的测试的所有元素
不访问空槽位。
const array = [1, 2, 3, 4, 5];
const evenNumbers = array.filter(element => element % 2 === 0);
console.log(evenNumbers); // 输出: [2, 4]
3.24 find / findLast
find,返回数组中满足提供的测试函数的第一个元素的值。否则返回undefined
findLast,与find使用相同,但它是反向迭代数组
空槽会被访问的,并被视为 undefined
。
const array = [, , 5, 12, 8, 130, 44]
console.log(array.find((e) => e > 10)) // 12
console.log(array.find((e) => e === undefined)) //undefined
3.25 findIndex / findLastIndex
findIndex,返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回 -1
findLastIndex,与findIndex使用相同,但它是反向迭代数组
空槽会被访问的,并被视为 undefined
。
const array = [, , 5, 12, 8, 130, 44]
console.log(array.findIndex((e) => e > 10)) // 3
console.log(array.findIndex((e) => e === undefined)) //0
3.26 some
some,测试数组中是否至少有一个元素通过了由提供的函数实现的测试。返回一个布尔值
不会在空槽上运行它的断言函数
const numbers = [1, 2, 3, 4, 5];
const hasEven = numbers.some(function(number) { return number % 2 === 0;
});
console.log(hasEven); // trueconsole.log([1, , 3].some((x) => x === undefined)) // falseconst obj = {length: 3,0: 'a',1: 'b',2: 'c',3: 'd'
}
const r = Array.prototype.some.call(obj, (e) => e == 'a')
console.log(r) //true
3.27 every
every,测试一个数组内的所有元素是否都能通过指定函数的测试 。如果有一个元素不满足条件,就会立即返回false
,并且停止遍历数组。
不会在空槽上运行它的断言函数,但对于空数组,不管测试条件如何,都会返回true
const numbers = [2, 4, 6, 8, 10]
const allEven = numbers.every(function (number) {return number % 2 === 0
})
console.log(allEven) // trueconsole.log([].every(() => {})) // true
//不会在空槽上运行假言判断,故相当于只有undefined一个元素
console.log([, , , undefined].every((x) => x === undefined)) // trueconst obj = {length: 3,0: 'a',1: 'b',2: 'c',3: 'd'
}
const r = Array.prototype.every.call(obj, (e) => e == 'a')
console.log(r) //false
3.3 非修改方法【2】
1. 这些方法不会对原数组进行修改,
3.31 indexOf / lastIndexOf
indexOf,返回数组中第一次出现给定元素的下标(可指定开始索引),如果不存在则返回 -1。
lastIndexOf,与indexOf使用相同,但它是反向迭代数组
不会访问空槽
let fruits = ['Apple', 'Banana', 'Mango', 'Orange', 'Banana']
console.log(fruits.indexOf('Banana')) // 1
console.log(fruits.indexOf('Banana', 2)) // 4,因为从索引 2 开始查找console.log(fruits.lastIndexOf('Banana')) // 4
console.log(fruits.lastIndexOf('Banana', 2)) // 1,因为从索引 2 开始向前搜
3.32 reduce / reduceRight
reduce,对数组中的每个元素按序执行一个提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。
会跳过空槽,但不会跳过 undefined
reduceRight,与reduce使用相同,但它是反向迭代数组
arr.reduce(reducer(),initialValue)
reducer:回调函数可接收四个参数:acc, cur, index(可选), array(可选)acc:累加器累加回调的返回值,它是上一次调用回调时返回的累积值,或initialValuecur:正在处理的元素index(可选):正在处理元素的索引。如果提供initialValue,起始index为0,否则为1array(可选):调用reduce的数组initialValue(可选):作为第一次调用reducer时第一个参数的值
-------------------------------------------------------------
const array = [1, 2, 3, 4]
const reducer = (acc, cur) => acc + cur
// 1 + 2 + 3 + 4
console.log(array.reduce(reducer)) // 10
// 初始值为 100
console.log(array.reduce(reducer, 100)) //110
-------------------------------------------------------------
const array = [[0, 1],[2, 3],[4, 5]]
const flatten = (acc, cur) => acc.concat(cur)
console.log(array.reduce(flatten, [])) //[0, 1, 2, 3, 4, 5]
// reduceRight,反向迭代数组
console.log(array.reduceRight(flatten, [])) // [4, 5, 2, 3, 0, 1]
3.33 slice
slice,返回一个由 起始索引start 和到 结束索引 end (不包括 end)的浅拷贝新数组
它接受负整数作为索引,具体选值情况和 cpoyWithin 方法的相似
不会跳过空槽,如果源数组是稀疏数组(有空槽的数组),返回的数组也会是稀疏数组
和 非修改方法【1】中的方法一样,可以改变this指向来提取伪数组的元素
let sliceArr = [1, 2, 3, 4, 5]
let sliced1 = sliceArr.slice(0, 3) //[1,2, 3]
let sliced2 = sliceArr.slice(1) //[2, 3, 4, 5]
let sliced4 = sliceArr.slice(-4, -1) //[2, 3, 4]
let sliced3 = sliceArr.slice(-4) //[2, 3, 4, 5]const obj = {length: 3,0: 'a',1: 'b',2: 'c',3: 'd'
}
const r = Array.prototype.slice.call(obj, 1, 3)
console.log(r) // ['b', 'c']
3.34 flat
flat,根据指定深度递归地将所有子数组元素拼接到新的数组中并返回。
如果传递给 flat 的参数是 Infinity,则无论多少层嵌套的数组都会被扁平化为一维数组。
如果扁平化的层数大于实际嵌套的层数,当扁平化的层数大于实际嵌套的层数时,flat 方法会在完全扁平化数组后立即停止并返回结果,而不会继续无意义地调用扁平化函数
该方法会删除数组中的空槽
const arr = [1, , [2, , [3, [4, , , ,]]]]
console.log(arr.flat()) //[1, 2, [3, [4,空x3]]]
console.log(arr.flat(2)) //[1, 2, 3, [4, 空 ×3]]
console.log(arr.flat(Infinity)) //[1, 2, 3, 4]
3.35 includes
includes,判断一个数组是否包含一个指定的值
将稀疏数组中的空位槽视为undefined
arr.includes(searchElement, fromIndex)
searchElement:检索的元素
fromIndex:检索起始索引,默认为 0。接受负索引
-------------------------------------------------
const arr = [1, 1, 2, 3, 4, 4, 3];
console.log(arr.includes(2));//trueconsole.log(arr.includes(2, 3)); //falseconsole.log([1, , 3].includes(undefined)); // true
3.36 toSpliced
toSpliced,是splice的复制版本,区别在于它不会返回删除的元素,而是返回经过删除、插入操作后的一个新数组,而不会更改原数组。
且,它会删除空槽位,并将其替换为undefined
// 删除
let testArray = [1, 2, 3, 4, 5, 6, , ,]
console.log(testArray.toSpliced(2, 3)) //[1, 2, 6, undefined, undefined]
// 插入
console.log(testArray.toSpliced(2, 0, 'a', 'b'))
//[1, 2, 'a', 'b', 3, 4, 5, 6, undefined, undefined]
// 替换
console.log(testArray.toSpliced(2, 1, 'a', 'b'))
//[1, 2, 'a', 'b', 4, 5, 6, undefined, undefined]
//原数组没改变
console.log(testArray) // [1, 2, 3, 4, 5, 6,空 ×2]]
3.37 join
join,将数组的元素连接成一个字符串,以指定的分隔符进行分隔。默认的分隔符是逗号。
将空槽视为 undefined,并产生额外的分隔符
和 非修改方法【1】中的方法一样,可以改变this指向来连接伪数组的元素成为字符串
const joinArr = [1,2,3,4,5]
console.log(joinArr.join()); //'1,2,3,4,5'
console.log(joinArr.join('|')) //1|2|3|4|5console.log([, , 666, undefined, undefined].join('|')) //||666||const obj = {length: 3,0: 'a',1: 'b',2: 'c',3: 'd'
}
const r = Array.prototype.join.call(obj, '')
console.log(r) // abc
3.38 contact
concat,合并两个或多个数组。而是返回被合并数组的一个新数组。
如果任何源数组是稀疏的,则结果数组也将是稀疏的
var array1 = ['a', 'b', 'c']
var array2 = ['d', 'e', 'f']
var array3 = array1.concat(array2)
console.log(array3)
// 输出: ["a", "b", "c", "d", "e", "f"]
console.log(array1)
// 输出: ["a", "b", "c"] (原数组不会被修改)
4. 其他方法
4.1 Array.isArray
该方法用于确定一个值是否是一个数组,对于检测数组类型非常有用。
const testArray= []
console.log(Array.isArray(testArray)) //true
4.2 Math对象方法
虽然Math
对象的方法不是直接用于数组操作的,但你可以使用它们来处理数组中的数值
Math.max(...array)
和Math.min(...array)
:使用扩展运算符(...
)将数组元素作为单独的参数传递给Math.max()
和Math.min()
,以找到数组中的最大值和最小值。Math.floor()
,Math.ceil()
,Math.round()
等:用于对数组中的数值进行四舍五入、向下取整或向上取整操作。
let numbers = [3.14, 4.99, -1.56, -2.45]
console.log(Math.max(...numbers)) //4.99
console.log(Math.min(...numbers)) //-2.45
console.log(numbers.map(Math.floor)) // [3, 4, -2, -3]
console.log(numbers.map(Math.ceil)) // [4, 5, -1, -2]
console.log(numbers.map(Math.round)) // [3, 5, -2, -2]
如有不足或遗漏,烦请私信或评论留言😊