一、JS中的数组有何特性?
1、不是原始类型,而是一种Array对象
let arr=[1,2,3,4];
typeof arr; //对数组使用typeof函数时,其返回值为'object'
(1)可以将数组的索引看作是对象中的属性名。
2、可以调整大小,并且可以包含不同的数据类型
(1)数组是一种对象,不像C++的底层那样是一片连续的内存,所以其可以调整 长度 和包含不同的 数据类型 。
3、复制操作创建的是浅拷贝
(1)首先,复制操作指的是展开语法
、Array.from()
、Array.prototype.slice()
和Array.prototype.concat()
等。
(2)赋值操作
不是复制操作,仅仅只是复制了对应数组的引用,连顶层元素也没有复制。
(3)浅拷贝的意思是只有顶层(浅层)属性会被复制,而深层的/嵌套的值与其源头共享。
(4)这就意味着修改顶层值不会引起源数据变化,但是修改底层值/嵌套值会引起源数据变化。
(5)浅拷贝和深拷贝是相对引用类型而言的,如果不是引用类型,那么浅拷贝和深拷贝其实是一样的(因为原始类型无法嵌套,没有顶层值、深层值之说)。
4、使用示例理解数组的浅拷贝的特点
何为顶层值?arr[0], arr[1], arr[2]… 均为顶层值,相对的arr[0][0], arr[1][0][0] 等等是其深层值,相比顶层值,深层值进行了深层的嵌套。
(1)顶层值互相独立(你变我不变)
let arr=[[1,2],[[3,4],[5,6]],[7,8],9];
let nums=Array.from(arr); //对arr进行浅拷贝
nums[0]=1; //顶层值并没有共享,对顶层值的修改不会引起其他数组的变化
console.log(nums); //[1,[[3,4],[5,6]],[7,8],9] 变
console.log(arr); //[[1,2],[[3,4],[5,6]],[7,8],9] 没变
(2)深层值互相共享(你变我也变)
let arr=[[1,2],[[3,4],[5,6]],[7,8],9];
let nums=Array.from(arr); //对arr进行浅拷贝
nums[0][0]=2; //对非顶层的深层(嵌套)值,是共享的,是会互相引起变化的
console.log(nums); //[[2,2],[[3,4],[5,6]],[7,8],9] 变
console.log(arr); //[[2,2],[[3,4],[5,6]],[7,8],9] 变
所以,对于一维数组,使用复制操作浅拷贝出来的数组也是独立的数组。
5、JS中数组的赋值操作,浅拷贝与深拷贝的对比
操作 | 赋值 | 浅拷贝 | 深拷贝 |
---|---|---|---|
顶层值关系 | 共享(相互影响) | 独立 | 独立 |
深层值关系 | 共享 | 共享 | 独立 |
与原值相等性 | true | false | false |
二、JS数组的方法
1、创建数组
(1)数组字面量
let arr=[1,2,3,4,5];
(2)单个参数的Array构造函数(参数为数组长度)
let arr=new Array(3) //创建一个长度为3的数组
(3)多个参数的Array构造函数(参数将被视为需要包含到数组的给定元素)
let arr=new Array(1,2,3,4,5); //等同于let arr=[1,2,3,4,5]相比还是字面量方便
2、修改数组
下表列出了会修改原始数组的方法,以及相应的非修改方法(返回新数组):
功能 | 修改方法(修改原数组) | 相应的非修改方法(返回新数组) |
---|---|---|
使用参数对指定元素覆盖 | copyWithin() | 无 |
使用参数对全体元素填充 | fill() | 无 |
删除尾部元素 | pop() | slice(0, -1) |
添加尾部元素 | push(v1, v2) | concat([v1, v2]) |
反转数组中的元素 | reverse() | toReversed() |
删除头部元素 | shift() | slice(1) |
添加头部元素 | unshift(v1, v2) | toSpliced(0, 0, v1, v2) |
移除部分原元素并新增部分新元素 | splice() | toSpliced() |
对数组中元素排序 | sort() | toSorted() |
fill()
fill()
方法用一个固定值填充一个数组中从起始索引(默认为 0)到终止索引(默认为 array.length
)内的全部元素。它返回修改后的数组。
fill(value, start, end) //可参数缺省,可以只有1个或者2个参数
value
:用来填充数组元素的值。注意所有数组中的元素都将是这个确定的值:如果 value
是个对象,那么数组的每一项都会引用这个元素。(所以最好不要用fill来创建二维数组)
填充范围:[start
,end
)
start
和end
都可以是负数:负数的话从数组的末端开始计算
slice()
slice()
方法返回一个新的数组对象,这一对象是一个由 start
和 end
决定的原数组的浅拷贝(包括 start
,不包括 end
),其中 start
(默认0) 和 end
(默认array.length
) 代表了数组元素的索引。原始数组不会被改变。
slice(start, end) //可以参数缺省,省略全部参数或者只有一个参数
start
和end
都可以是负数:负数的话从数组的末端开始计算
splice()
splice()
方法就地移除或者替换已存在的元素和/或添加新的元素。
splice(start, deleteCount, item1, item2, /* …, */ itemN) //可参数缺省
start
:从 0 开始计算的索引,表示要开始改变数组的位置,可使用负数
deleteCount
:需要删除的元素数量,被省略后从 start 到数组末尾的所有元素将被删除
item1
、… 、itemN
:从 start 开始要加入到数组中的元素。被省略的话,只删不加
sort()
默认的sort()
let nums=[1,2,43,4,5,6,6,7,8];
nums.sort();
console.log(`[${nums.join()}]`); //[1,2,4,43,5,6,6,7,8]
sort()
方法就地对数组的元素进行排序,并返回对相同数组的引用。默认排序是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序。- 所以这是43在5前面的原因,其实比较的是4和5。
带比较函数为参数的sort(比较函数
)
let nums=[1,2,43,4,5,6,6,7,8];
nums.sort((a,b)=>{return a-b}); //按照升序
console.log(`[${nums.join()}]`); //[1,2,4,5,6,6,7,8,43]
let nums=[1,2,43,4,5,6,6,7,8];
nums.sort((a,b)=>b-a); //按照降序
console.log(`[${nums.join()}]`); //[43,8,7,6,6,5,4,2,1]
(a,b)=>{return a-b}
和(a,b)=>b-a
都是比较函数的一种;- 比较函数有两个参数,即两个需要比较的值;
- 比较函数的返回值为正值时,a会被排到b的后面;负值时,a会被排到b的前面;0时,位置不会发生变化。
3、迭代方法和遍历方法
map()
map()
方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。
map(callbackFn, thisArg) //map((element, index, array)=>{ 函数内容 }, thisArg)
callbackFn
:为数组中的每个元素执行的函数。它的返回值作为一个元素被添加为新数组中。该函数被调用时将传入以下参数:
element
:数组中当前正在处理的元素。index
:正在处理的元素在数组中的索引。array
:调用了map()
的数组本身。
thisArg
:执行 callbackFn
时用作 this
的值。
every()
every()
方法测试一个数组内的所有元素是否都能通过指定函数的测试。它返回一个布尔值。
every(callbackFn, thisArg) //every((element, index, array)=>{ 函数内容 }, thisArg)
callbackFn
:为数组中的每个元素执行的函数。它应该返回一个真值以指示元素通过测试,否则返回一个假值同时every()
方法将会立即返回 false 并停止遍历数组。
some()
与every()
相对,every遇到false会立刻返回,而some遇到true会立刻返回,some()
方法测试数组中是否至少有一个元素通过了由提供的函数实现的测试。如果在数组中找到一个元素使得提供的函数返回 true,则返回 true;否则返回 false。它不会修改数组。
filter()
filter()
方法返回新数组,其中的元素是原数组中满足判断条件的元素
filter(callbackFn, thisArg) //filter((element, index, array)=>{ 函数内容 }, thisArg)
callbackFn
:为数组中的每个元素执行的函数。它应该返回一个真值以将元素保留在结果数组中,否则返回一个假值。
find()
find()
方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
find(callbackFn, thisArg) //find((element, index, array)=>{ 函数内容 }, thisArg)
callbackFn
:为数组中的每个元素执行的函数。它应该返回一个真值来表示已经找到了匹配的元素。
同类方法:
filter()
会返回所有满足条件的值,而find()
只会返回符合条件的第一个findIndex()
会返回索引的值,而不是元素的值,没找到会返回 -1。findLast()
:返回最后一个满足条件的元素findLastIndex()
:返回最后一个满足条件的索引
flatMap()
flatMap()
方法对数组中的每个元素应用给定的回调函数,然后将结果展开一级,返回一个新数组。它等价于在调用 map()
方法后再调用深度为 1 的 flat()
方法(arr.map(…args).flat()),但比分别调用这两个方法稍微更高效一些。
flatMap(callbackFn, thisArg) //flatMap((element, index, array)=>{ 函数内容 }, thisArg)
callbackFn
:一个在数组的每个元素上执行的函数。它应该返回一个包含新数组元素的数组,或是要添加到新数组中的单个非数组值。
flat()
方法创建一个新的数组,并根据指定深度递归地将所有子数组元素拼接到新的数组中。
flat(depth) //depth:指定要提取嵌套数组的结构深度,默认值为 1
forEach()
forEach()
方法对数组的每个元素执行一次给定的函数。
forEach(callbackFn, thisArg) //forEach((element, index, array)=>{ 函数内容 }, thisArg)
callbackFn
:为数组中每个元素执行的函数。并会丢弃它的返回值。
forEach()
不会改变其调用的数组,但是,作为 callbackFn
的函数可以更改数组。
不能继续链式调用,其典型的用法是在链式调用的末尾执行某些操作。
forEach()
期望的是一个同步函数,它不会等待 Promise 兑现。
reduce()
reduce()
方法对数组中的每个元素按序执行一个提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。
第一次执行回调函数时,不存在“上一次的计算结果”。如果需要回调函数从数组索引为 0 的元素开始执行,则需要传递初始值。否则,数组索引为 0 的元素将被用作初始值,迭代器将从第二个元素开始执行(即从索引为 1 而不是 0 的位置开始)。
reduce(callbackFn, initialValue) //reduce((accumulator, currentValue, currentIndex, array), initialValue)
callbackFn
:为数组中每个元素执行的函数。其返回值将作为下一次调用 callbackFn 时的 accumulator 参数。对于最后一次调用,返回值将作为 reduce() 的返回值。该函数被调用时将传入以下参数:
accumulator
:上一次调用callbackFn
的结果。在第一次调用时,如果指定了initialValue
则为指定的值,否则为array[0]
的值。currentValue
:当前元素的值。在第一次调用时,如果指定了initialValue
,则为array[0]
的值,否则为array[1]
。currentIndex
:currentValue
在数组中的索引位置。在第一次调用时,如果指定了initialValue
则为 0,否则为 1。array
:调用了reduce()
的数组本身。
initialValue
:初始值
reduceRight()
是reduce()
相反方向迭代的实现
4、其他遍历方法
for...of
和for...in
,区别是for...in
还会遍历原型链,数组中一般使用for...of
即可。
各种迭代方法起始也都是遍历方法,只不过功能不同,返回值类型可能不同。
5、复制数组(复制操作)
let arr=[1,2,3,4,5]
let nums1=[...arr];
let nums2=Array.from(arr);
let nums3=arr.slice();
let nums4=arr.concat();
let nums5=arr.filter(()=>true);
let nums6=arr.flat();
let nums7=arr.flatMap((a)=>a);
let nums8=arr.map((a)=>a);
let nums9=arr.toSpliced();
6、将数组转换为字符串
join()
join()
方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串,用逗号或指定的分隔符字符串分隔。如果数组只有一个元素,那么将返回该元素而不使用分隔符。
join(separator) //separator:指定一个字符串来分隔数组的每个元素,如果省略,数组元素用逗号(,)分隔
会将数组完全展开为一维数组,然后分隔后连接成字符串。
7、部分方法可以被链式调用
关于JS中二维数组的使用,我写在了这篇博客
相信通过对二维数组的了解,大家可以对深拷贝和浅拷贝的理解更上一层