数组
1、创建数组
在JavaScript中,创建数组有多种方式,先来看最简单的一种,代码如下:
js
let arr=[1,5,8]
如果没有在[]中存放元素,则定义了一个空的数组,例如:let arr=[]。这样的数组中没有任何元素,可以在后边使用数组的访问语法进行元素的添加和删除。访问数组的长度可以使用数组中的length属性,例如arr.length会返回3。
由于JavaScript是动态类型语言,所以不限制数组元素的类型,可以同时存放多种类型的数据,不过推荐存放统一类型的数据,因为对于数组每个元素的操作一般是相同的,如果混入了其他类型的元素可能会出错。
第2种方式是使用Array()构造函数创建数组。构造函数是用于初始化对象的特殊函数。使用构造函数定义数组的代码如下:
js
let arr=new Array("a","b","c");//["a","b","c"]
let data=new Array(1,2,3); //[1,2,3]
小括号中逗号隔开的参数会作为新创建的数组的初始元素。不过使用Array()构造函数创建数组的时候要注意,它有两种形式,一种是像上方一样接收多个参数,作为初始元素。另一种形式则是只接收一个数字类型的参数,用于指定数组的长度,它会创建指定长度的数组,但是每个元素都是未初始化的,只是预留了空位(Empty),需要在后面给空位赋值,例如创建一个长度为5的空数组,代码如下:
js
let arr=new Array(5);//[<5 empty items>]
通过上例可以清楚地看到,无法使用Array()构造函数创建只有一个数字元素的数组,因为它会作为长度参数而不是初始元素,对于这种情况,Array对象中提供了以Array.of()创建数组的方式,传给它的参数都将作为数组的初始元素,例如创建只有一个数字元素的数组,代码如下:
js
let arr=Array.of(5);//[5]
第3种方式是使用Array.from(),适合将其他数组中的元素复制到新数组的情况。这种方法接收一个其他数组、Array-like(类数组结构)或可迭代的对象作为参数
js
let arr1=Array.from([3,4,5]);
arr1; //[3,4,5]
let arr2=Array.from(new Set(["a","c","d"]));
arr2; //["a","c","d"]
arr1直接复制了普通数组[3,4,5]的元素,arr2则复制了Set这种数据结构中的所有元素,因为Set是可迭代的对象,所以可以进行复制。
Array.from()还可以额外接收2个参数,第2个参数用于指定一个函数,它会在复制元素时对每个元素进行调用。该函数与6.6节要讲的map()方法类似,不过它接收2个参数,分别是当前要复制的元素和索引,而第3个参数则通过Array.from()的第3个参数进行传递。例如在复制一个数组的同时,把每个元素乘以它的索引再放到新数组中,代码如下:
js
let arr=Array.from([1,2,3],(v,i)=>v * i);
arr; //[0,2,6]
在执行上方代码中的复制时,第1个元素的值为1,索引为0,即函数中的v为1,i为0,v乘以i的结果就为0,后边第2个元素、第3个元素的计算结果则分别为2和6,放到新数组中就成为[0,2,6]了。
需要注意的是,Array.from()执行的是浅复制,也就是说数组中的元素如果是对象类型,则只会复制它们的内存地址,新数组中的对象还是指向原对象,对它们进行修改仍然会引起原对象的修改,代码如下:
js
let arr=[{a:1},{b:2}];
let arr2=Array.from(arr);
arr2[0].a=2;
arr[0]; //{a:2}
可以看到当对使用Array.from()复制后的数组arr2中的对象进行修改时,原数组arr中对象的对应属性值也被修改了。
虽然数组有多种创建方式,但是使用Array()构造函数和Array.of()的方法创建起来很烦琐,所以推荐使用[]语法定义数组。另外需要注意的是,JavaScript支持的数组最大长度为232-1。
由于数组元素可以是任何类型,所以数组也可以嵌套子数组,代码如下:
js
let arr=[1,[2,3],4];//[1,[2,3],4]
2、访问数组
访问数组使用数组名加[],里边写上数字索引(Index),数组的第1个元素的索引是0,最后一个元素的索引是数组的长度减1。使用索引访问数组元素的代码如下:
js
let arr=[2,6,8];
arr[0]; //2
arr[2]; //8
索引不必是数字字面值,像内容为数字的字符串、存放了数字的变量等任何可以自动转换为数字的表达式都可以作为索引值,代码如下:
js
let arr=[1,2,3,4,5];
arr["0"]; //1
arr[arr[2]]; //4,相当于arr[3]
let index=1;
arr[index]; //2
如果访问时超出了最大可索引的元素,或者使用了负数索引,则会直接返回undefined,例如使用arr[-1]或arr[6]访问上例中的元素都会返回undefined。
3、修改元素
数组定义好之后,还可以修改元素的值,也可以添加和删除元素。修改元素与访问元素的语法类似,只需使用=进行赋值,例如,修改一个数组中第2个元素的值,代码如下:
js
let arr=[3,6,7,9];
arr[1]=10;
arr; //[3,10,7,9]
如果给一个大于数组长度的索引进行赋值,则数组的长度会自动增长为该索引加1,并把新添加的值作为数组的最后一个元素,而这个元素与原数组的最后一个元素之间会使用空元素进行占位,访问它们的值均为undefined,对于这种数组,可以将它们称为稀疏数组(Sparse Array),代码如下
js
let arr=[1];
arr[5]=2;
arr; //[1,empty×4,2]
arr.length; //6
数组的长度最后变成了6,第1个元素为1,最后一个为2,中间为4个空白位置,最后可以使用length属性访问数组的长度,结果为6,即5+1。
关于稀疏数组的定义,在使用[]定义数组时,也可以通过忽略某个元素制造空位实现稀疏数组,代码如下:
js
let arr=[1,,3];//[1,empty,3]
1和3之间的元素为空白,直接使用逗号略过了。
需要注意的是,因为数组本质上也是对象,所以也可以像对象一样添加属性,这样就不是给数组添加元素了,而是添加到了数组对象的属性中,除了数字索引外,像字符串、负数等都会作为属性添加到数组对象中,例如给上例中的arr添加一个-1和一个prop属性,那么在打印数组的值时,-1和prop会在数组元素的末尾以冒号(属性)形式展示出来,代码如下:
js
arr[-1]=5;
arr.prop=3;
arr; //[1,empty×4,2,-1:5,prop:3]
4、删除元素
4.1、delete运算符
使用delete运算符删除数组中的元素,可以在数组访问的语法基础上,在前边加上delete关键字。例如删除数组中第2个元素,代码如下:
js
let arr=[1,2,3];
delete arr[1]; //返回值为true,arr变为[1,empty,3]
arr.length; //3
可以看到使用delete运算符删除元素之后,对应索引的元素空位会保留下来,后边的元素并不会向前移动,数组的长度也不会发生变化。另外判断对应索引的元素是否存在于数组中,可以使用in运算符。在in运算符前边写上索引,后边写上进行判断的数组,如果存在则会返回true,如果不存在则返回false,例如判断上例中索引为1的元素是否还存在于arr数组中,代码如下:
js
1 in arr;//false
arr[i]; //undefined
可以看到索引为1的元素不存在了,访问它的值会返回undefined,但是要注意的是,虽然arr[1]返回了undefined,但是因为使用delete运算符删除了它而导致它不存在,如果直接把该位置的元素设置为undefined,则它还会存在于数组中,只不过值为undefined,代码如下:
js
let arr=[1,2,3];
arr[1]=undefined;
1 in arr; //true
arr[1]; //undefined
可以看到arr[1]的值仍为undefined,但是使用in判断时,1这个索引位置的元素还是存在于数组中的。
4.2、splice()方法
如果要删除元素并使后续元素向前移动,则可以使用splice(),可以给它的第1个参数传递要删除的元素的起始索引,第2参数传递要删除的数量,例如使用splice()实现删除数组中的第2个元素,代码如下:
js
let arr=[1,2,3];
arr.splice(1,1);//[2]
arr; //[1,3]
arr变成了[1,3],splice()会返回被删除的元素,因为它支持删除多个元素,所以返回的结果是包含被删除元素的数组,如果只删除了一个元素,则也会把它放到结果数组中,例如上例中splice()的返回值为[2]。
在调用splice()方法之后,原数组则变成了除掉被删除的元素外所剩下的元素。这种改变原数组的操作,称为原地(In-Place)操作,不过数组对象提供的大部分操作都是返回新的数组,并不会改变原数组。
splice()的第1个参数也可以是负数,这样就会从最后一个元素索引开始向前计算起始索引,其他操作与传递正数时一样。
如果传递给splice()的第2个参数大于数组的长度,或者没有传递,则会从起始索引开始,删除它后面所有的元素,例如删除一个数组中索引为3的元素及后面所有的元素的代码如下:
js
let arr=[1,2,3,4,5];
arr.splice(3); //[4,5]
arr; //[1,2,3]
splice()还可接收第3个参数,它是一个变长的参数,用于指定要添加的元素,即在删除元素之后,从第1个参数指定的位置再添加新的元素,后面的元素会顺序后移,因此splice()还支持添加和替换元素。如果要添加新元素,则只需把第2个参数,即要删除的数量,设置为0,然后后边的参数传递要添加的元素。例如,从索引为2的位置插入3个新的元素,代码如下:
js
let arr=[1,3,5,7];
arr.splice(2,0,2,4);//[]
arr; //[1,3,2,4,5,7]
这时,因为要删除的元素的数量为0,所以splice()会返回一个空的数组,原数组则原地新增了3个元素,上方代码把2和4放到了索引为2、3的位置,然后5和7分别向后移动了1位。
如果要替换一些元素,则只需让删除的数量等于新添加的元素的数量,代码如下:
js
let arr=[1,3,5,7];
arr.splice(2,2,2,4);//[5,7]
arr; //[1,3,2,4]
这里从索引为2的位置删除了2个元素,即5和7,之后在索引为2的位置添加了2个新的元素2和4,最后结果就是[1,3,2,4]。
最后,如果给splice()的参数设置了大于或等于数组长度的数值,则splice()会直接从数组末尾开始添加元素,无论第2个参数(要删除的数量)设置为多少,都不会删除元素,代码如下:
js
let arr=["a","b","c"];
arr.splice(3,10,"d","e","f");//[]
arr; //["a","b","c","d","e","f"]
4.3、修改length属性
删除数组元素还有一种不常用的方式,使用length属性。数组在创建之后,它自己会维护length属性的值,当有元素添加进来时,会自动增加length的值,而当有元素被删除时,则会自动减少,而反过来也可以通过修改length属性的值来删除超过length数量的元素,这种只适合删除末尾的元素,代码如下:
js
let arr=[1,2,3,4];
arr.length; //4
arr.length=2;
arr; //[1,2]
arr.length=0;
arr; //[]
把arr.length设置为2之后,超过2个的元素直接被舍弃了。当把数组的length设置为0时,数组就变成了空数组。
5、栈和队列模式
数组中的push()方法会把元素原地添加到数组的末尾,并返回添加新元素后数组的长度,它接收一个变长参数,可以同时入栈多个元素。pop()方法则从数组末尾原地删除一个元素,并返回删除的元素,它不需要参数。使用数组作为栈的基本操作代码如下:
js
let stack=[];
stack.push(1,2,3); //3,stack:[1,2,3]
stack.pop(); //3
stack; //stack:[1,2]
stack[stack.length-1];//2,查看栈顶元素
需要注意的是,当栈或数组为空时,再调用pop()会返回undefined,不过此时应该与length属性进行综合判断,因为pop()有可能返回的是数组中某个值为undefined的元素,数组此时并不为空。
除了用于实现栈模式之外,push()多用于在初始化数组之后向数组添加新元素,代码如下
js
let arr=[];
for(let i=0;i<5;i++){arr.push(i)}
arr; //[0,1,2,3,4]
数组也可以实现队列(Queue)模式。队列是与栈类似的数据结构,与栈相反,它是先进先出的(First In First Out,FIFO),即先入队的元素先出队,就如同排队一样。
使用数组实现队列主要使用unshift()入队和shift()出队方法,跟push()和pop()语法一样,只是它们从数组的头部操作。unshift()会把元素添加到数组的开始,shift()则会删除并返回数组的第1个元素。队列模式下的操作代码如下:
js
let queue=[];
queue.unshift(1,2,3);//3,queue:[1,2,3]
queue.shift(); //1,queue:[2,3]
queue[0]; //2,查看队首元素
6、遍历数组
访问数组中的每个元素的过程称为遍历或迭代(Iteration),最简单直观的方式是使用for循环,例如遍历整个数组,可以把指示变量初始化为0作为索引,指向数组的第1个元素,然后每次循环后索引加1,在索引大于或等于数组长度的时候退出,代码如下:
js
let arr=[1,2,3];
for(let i=0;i<arr.length;i++){
console.log(arr[i]);
}
遍历数组还可以使用for...of循环。for...of循环是ES6中出现的,使用它遍历数组的示例代码如下:
js
let arr=["a","b","c"];
for(let item of arr){
console.log(item);
}
6.1、forEach()
avaScript数组中还提供了函数式的方式进行遍历,它们有不同的作用和使用方式,在用法上需要注意并加以区分。类似于使用for循环的方式进行遍历数组的方法是forEach(),它接收1个函数作为参数,即回调函数,对于数组中的每个元素都会调用一次该函数。forEach()会给回调函数传递3个参数,第1个是当前遍历到的元素,第2个是当前元素的索引,第3个是数组本身,一般只用到前两个参数,forEach()没有返回值。使用forEach()遍历数组的代码如下:
js
let arr=[3,5,8,9];
arr.forEach((ele)=>console.log(ele));
可以看到使用函数式的方式,再加上箭头函数,可以使代码更简洁。不过要注意的是,使用forEach()函数没有提供中断循环的机制,只能使用普通的for循环或者for...of循环结合break语句来中断。
forEach()本身不会修改原数组的内容,但是可以在里边人为地修改原数组,例如使用push()、pop()、unshift()、splice()添加、删除或替换元素时,会影响数组的遍历,代码如下:
js
[1,2,3].forEach((v,i,arr)=>{
console.log(v);
arr.unshift(4);
});
1
1
1
示例中使用unshift()方法给数组头部添加元素,会导致元素向后移动,所以当前元素会重复遍历,添加几个就重复遍历几次,不过遍历次数仍然是原数组的长度,并不是添加元素后的新长度,假如使用push()给数组末尾添加元素,那么新添加的元素永远不会遍历到。如果删除了已经遍历过的元素,此时删除了几个则会少遍历几个。
对于后边类似forEach()的方法也是如此,这种操作会导致难以发现的错误,所以推荐如非必要,尽量维持数组原状。
6.2、map()
与forEach()类似的还有map(),参数要求与forEach()一样,其区别是map()有返回值,它返回一个新的数组,里边的每个元素是每次调用回调函数时的返回值。通常使用map()对数组进行变换(Transform)操作,例如下方代码展示了把一个数组中的每个元素进行加2操作,并返回结果数组,代码如下:
js
let arr=[1,2,3,4];
let newArr=arr.map(v=>v+2);
newArr; //[3,4,5,6]
也可以再打印一下原数组arr,会发现它的值没有变化,因为map()会返回新的数组,并不会原地操作原数组。
对于有空位的数组,map()和forEach()并不会对空位执行回调:
js
console.log([1,,2].map((v)=>v+1)); //[2,<1 empty item>,3]
console.log(new Array(5).map((v)=>v+1));//[<5 empty items>]
6.3、reduce()
reduce()可以对数组中的元素进行遍历,然后返回合并后的单一结果。
reduce()回调函数中的参数与map()、forEach()中的参数有所不同,它的第1个参数是累计值,即每次调用回调函数后返回的值,后面3个参数与map()和forEach()的参数一样。reduce()除了可接收回调函数外,还可接收第2个参数,用于指定第一次调用回调函数时累计值的初始值。例如,计算数组中所有元素的和,代码如下:
js
let arr=[1,2,3,4,5];
let sum=arr.reduce((acc,cur)=>acc+cur,0);
console.log(sum); //15
上例中,给reduce传递的第2个参数值为0,代表初始的和为0,当第一次调用回调函数时,累计值acc的值则为0,当前元素cur为1,在执行acc+cur之后,回调函数返回1,作为第2次调用回调函数时acc参数的值,然后在第2次调用中,使用acc加上当前元素的值2,所以返回3,以此类推,直到计算到5,返回10加5的结果,即15。
如果没有给reduce()传递第2个参数,则reduce()的回调函数的参数含义会有所不同,第一次调用时,第1个参数变为了数组的第1个元素的值,第2个参数变成了第2个元素的值,之后的每次调用中,则与之前的例子一样,第1个参数就重新变为了累计值,第2个参数重新变为了当前遍历到的元素。这样,上边求和的代码也可以省略reduce()的第2个参数,变为arr.reduce((acc,cur)=>acc+cur),结果一样。
结合map()和reduce()可以实现先变换后归并的操作,代码如下:
js
[1,2,3,4].map(v=>v*2).reduce((acc,cur)=>acc+cur,0);//20
这样就形成了对数据的流式(Stream)处理操作,因为数组中有些方法会返回数组本身,例如filter()、slice()、map()等,可以对数据进行连续处理,例如过滤、筛选、变换等。
利用reduce()还可以标准化(Normalize)数据。在前端开发中,尤其是当使用类似redux状态管理库时,需要改变接收的后端数据来方便更新状态,让数据更符合数据库的存储逻辑以方便查找。这里以todo列表为例,代码如下:
js
const todos=[
{id:1,name:"todo1",completed:true},
{id:2,name:"todo2",completed:false},
{id:3,name:"todo3",completed:true},
{id:4,name:"todo4",completed:false},
]
如果要修改其中某个todo的内容,则需要使用find()或者findIndex()方法根据id查找到对应的todo然后进行修改。这时,可以把todo列表数据标准化为一个对象,todo的id作为key,值为对应的todo对象,代码如下:
js
{
1:{id:1,name:"todo1",completed:true},
2:{id:2,name:"todo2",completed:false},
3:{id:3,name:"todo3",completed:true},
4:{id:4,name:"todo4",completed:false},
}
这时再修改其中某个todo的内容时,只需使用todos[id]把id改为对应的todo id就可以快速定位到该todo,对于字符串类型的id值同样适用。使用reduce()实现这个标准化的过程的代码如下:
js
const normalizedTodos=todos.reduce((acc,todo)=>{
acc[todo.id]=todo;
return acc;
},{});
7、过滤和测试
7.1、filter()
如果想获取数组中所有满足条件的元素,则可以使用filter()对数组进行过滤。filter()也接收一个回调函数作为参数,且回调函数的参数与map()也一样。filter()的返回值也是一个新的数组,每个元素是满足回调函数指定条件的元素,即回调函数返回结果为true的部分。例如获取一个数组中所有大于5的元素,代码如下:
js
let arr=[8,1,3,9,10,2];
let filteredArr=arr.filter((v)=>v>5);
console.log(filteredArr); //[8,9,10]
如果数组元素为对象类型,则可以根据对象的属性进行过滤,假设有一个todo待办事项列表,并过滤出已完成的todo项目:
js
const todos=[
{name:"todo1",completed:true},
{name:"todo2",completed:false},
{name:"todo3",completed:false},
{name:"todo4",completed:true},
]
const completed=todos.filter(todo=>todo.completed);
console.log(completed);
js
[
{name:"todo1",completed:true},
{name:"todo4",completed:true},
]
7.2、some() & every()
如果要测试整个数组是否满足一定条件,并返回布尔类型的true和false,则可以使用some()和every()。some()测试的是如果数组中有一个元素满足条件,就返回true,否则返回false,例如测试上例数组中是否至少有一个元素大于5,代码如下:
js
let test=arr.some((v)=>v>5);
console.log(test); //true
every()则是当数组中的每个元素都满足条件时才返回true,否则返回false。例如,测试上例数组中的元素是否都大于5,代码如下:
js
let test=arr.every((v)=>v>5);
console.log(test); //false
最后需要注意的是,some()和every()并不会遍历全部元素,而是当能确定结果的时候就会返回。在some()中,如果遇到一个元素返回了true,则结果直接返回true;在every()中,如果遇到一个元素返回false,则直接返回false。
8、数组排序
8.1、sort()
对数组排序使用sort()方法,它会把数组进行原地排序,然后返回排序后的数组,它与原数组是同一个数组。如果没有给sort()传递参数,则会把元素转换成字符串,按字符串的UTF-16代码点进行升序排列,所以要注意数字类型的元素可能会跟预期结果不同,因为数字被转换成了字符串进行比较,代码如下:
js
["apple","banana","car","app"].sort();//["app","apple","banana","car"]
[82,71,99,4,10,120].sort(); //[10,120,4,71,82,99]
可以看到第2个数组中,数字并没有按正常顺序排序,如果要对数字进行排序,则可以给sort()传递一个回调函数,回调函数接收两个参数,即两个需要比较的元素,假设第1个参数名为a,第2个为b,如果想让a排在b前边,就需要回调函数返回一个负数,如果让a排在b的后边就需要返回正数,而如果让a和b保持原来的位置,则返回0,不过不同的浏览器对于返回0的处理方式不同,有可能让a在前b在后,也有可能让b在前a在后,代码如下:
js
//chapter6/array_sort1.js
[82,71,99,4,10,120].sort((a,b)=>{
if(a>b)return 1;
if(a<b)return-1;
if(a===b)return 0;
});//[4,10,71,82,99,120]
8.2、reverse()
如果想反转一个数组,即最后一个元素作为第1个元素,倒数第2个元素作为第2个元素,以此类推,则可以使用reverse()方法,它会原地反转数组并返回反转后的原数组,与sort()一样会修改原数组,代码如下:
js
["h","e","l","l","o"].reverse();["o","l","l","e","h"]
9、数组连接
如果要合并多个数组,则可以使用concat()函数进行连接,它会把多个数组的值合并成一个新的数组并返回,并且不会改变原数组。concat()接收一个变长参数,它有两种形式,第1种是传递数组类型,它会把所有数组中的值取出来并放到一个新数组中,代码如下:
js
[1,3].concat([2,4],[5,8]);//[1,3,2,4,5,8]
这里需要注意的是,如果数组中有嵌套数组,则嵌套的数组会原样合并到新数组中,代码如下:
js
[1,2].concat([3,[4,5],6]);//[1,2,3,[4,5],6];
第2种是传递多个单一的值,它们会直接添加到调用concat()的数组中,代码如下:
js
[1,2].concat(3,4,5);//[1,2,3,4,5]
10、数组裁切
数组的裁切使用slice()方法,注意与splice()命名的区别。slice()方法接收两个参数,起始索引和结束索引,然后返回起始索引(包括)到结束索引(不包括)的元素组成的子数组,原数组不会发生变化。例如,返回数组中索引2~5的元素,代码如下:
js
[1,2,3,4,5,6].slice(2,5);//[3,4,5];
slice()的第2个参数可以省略,这样可以返回从起始索引开始的元素到最后一个元素所组成的子数组,代码如下:
js
[1,2,3,4,5,6].slice(2);//[3,4,5,6]
slice()的第1个参数也可以省略,这样起始索引是0,相当于返回了原数组的一个复制。起始索引和结束索引也可以是负数,-1代表最后一个元素,-2代表倒数第2个元素,以此类推。例如,返回索引2~4的子数组,代码如下:
js
[1,2,3,4,5,6].slice(2,-2);//[3,4,5];
如果起始索引大于数组的最大索引,则会返回空数组。如果结束索引大于数组的最大索引,则会返回从起始索引到最后一个元素所组成的子数组,相当于省略了第2个参数。
11、搜索元素
11.1、includes()
includes()用于判断一个元素是否存在于数组中,如果存在,则返回true,如果不存在,则返回false。它接收两个参数,第1个是要搜索的元素,第2个是起始索引,可以是负数并且可以忽略。includes()用法的代码如下:
js
[1,2,3].includes(2); //true
[1,2,3].includes(1,-1); //false
["a","c","c"].includes("c",1);//true
[1,NaN,2].includes(NaN); //true
11.2、indexOf()
indexOf()用于搜索某个元素所在的索引,如果成功找到了该元素,则返回该元素的索引,如果没找到则返回-1。它的参数与includes()所要求的一样,代码如下:
js
[1,2,3].indexOf(2); //1
[1,3,4,1,2].indexOf(1,2); //3
["hello","world"].indexOf("world",-1);//1
需要注意的是,indexOf()使用了严格相等的方式对比要搜索的元素和数组中的元素,即使用===,这就导致了无法像includes()中搜索NaN这样和自身不相等的值。例如:[1,NaN,2].indexOf(NaN)会返回-1。
11.3、lastIndexOf()
lastIndexOf()与IndexOf()的搜索顺序相反,它会从第2个参数指定的索引开始向前进行搜索,如果没有指定第2个参数,则从数组末尾开始向前进行搜索。
11.4、find()
find()用于搜索数组中满足一定条件的第1个元素的值,它接收一个回调函数作为参数,回调函数的3个参数分别是当前遍历到的值、索引和数组本身,与map()等方法的回调函数一样。如果找到了元素,则会返回该元素并停止搜索,如果没有找到则返回undefined。例如要查找数组中第1个大于或等于5的元素,代码如下:
js
[1,3,5,7].find(v=>v>=5);//5
11.5、findIndex()
findIndex()与find()的语法结构相同,只是返回结果为元素的索引,如果没有找到则会返回-1。findIndex()用法的代码如下:
js
[1,3,5,7].findIndex(v=>v>=5);//2
12、数组与字符串
利用数组可以方便地生成有规律的字符串。数组中提供了join()方法,用于按一定的结构把数组中的元素连接成一整串字符串,它接收一个参数,用于指定每个元素之间的连接符,可以省略,默认为半角逗号。join()用法的代码如下:
js
["hello","world"].join(); //"hello,world"
["a","b","c"].join(","); //"a,b,c"
[1,2,3].join("+"); //"1+2+3"
["this",undefined,"is",null].join("");//"this is"
注意最后一个示例,如果数组中有undefined、null等空值,则它们会以空格的形式存在于结果字符串中。把数组转换为字符串也可以调用toString()方法,它会返回以逗号分隔的数组元素连接成的字符串,相当于join(","),逗号后边没有空格,代码如下:
js
["a","b","c"].toString();//"a,b,c"
13、数组填充
如果想给数组快速添加相同的元素,则可以使用fill()方法,它接收3个参数,第1个是要填充的值,第2个是起始索引,默认为0,第3个是结束索引(不包括),默认为数组的长度,同样地,后两个参数也可以是负数。fill()会原地修改数组并返回填充后的数组,即原数组本身。例如,如果使用new Array()进行初始化的数组中没有元素,则可以使用fill()快速填充元素,代码如下:
js
let arr=new Array(5);//[empty×5]
arr.fill(0); //[0,0,0,0,0]
arr; //[0,0,0,0,0]
对于已经有元素的数组,也可以通过fill()快速地把一些元素替换掉。例如把一个数组中从索引2到索引4的元素替换成0,代码如下:
js
[1,2,3,4,5].fill(0,2,4);//[1,2,0,0,5]
14、数组复制
数组中提供了copyWith()方法用于原地复制数组中的一部分元素到指定的索引处,对应索引处的元素及后续等同于复制数量长度的元素会被覆盖,之后该方法会返回修改后的原数组。copyWith()方法接收3个参数,分别是要复制到的目标索引,要复制的元素的开始索引、结束索引(不包括)。例如把数组中索引为4~6的元素复制到索引为0的位置,代码如下:
js
let arr=["a","b","c","d","e","f","g"];
arr.copy W ithin(0,4,6);
arr; //["e","f","c","d","e","f","g"]
可以看到索引4~6的元素"e"、"f"、"g"复制到了数组索引0的位置,把之前的"a"、"b"和"c"覆盖掉了。
copyWithin()的第2个参数和第3个参数都可以省略,如果省略了第3个参数,则会复制第2个参数所指定的索引元素及后边所有的元素。如果第2个和第3个参数都省略了,则会复制从0到最后所有的元素。这两个参数也可以是负数,这样会从数组最后向前计算索引。
copyWithin()能够原地移动数组中的元素,性能比较好,适合在TypedArray中通过移动二进制数据来对文件、图片、网络数据等以二进制存储的数据进行操作。
15、扁平化
15.1、flat()
flat()接收1个参数,表示要展开的层数,默认为1层,即在最外层数组中,把第1层嵌套的数组元素取出来,而第1层嵌套的数组中的嵌套数组及更深层次的嵌套数组则保持原样,并不会扁平化。要想扁平化深层嵌套的数组可以把参数设置为对应的层数。flat()用法的代码如下:
js
["a",["b","c",],"d"].flat();//["a","b","c","d"]
[1,[2,[3,4]],5].flat(); //[1,2,[3,4],5]
[1,[2,[3,4]],5].flat(2); //[1,2,3,4,5]
如果想把所有嵌套的数组扁平化,则可以给flat()传递Infinity,表示无限大,这样就可以展开所有层数了。
15.2、flatMap()
flatMap()相当于先调用map()再调用flat(),不过使用flatMap()的效率稍高一些,并且它只支持展开1层,接收的参数与map()一样,是一个回调函数。flatMap()用法的代码如下:
js
[1,2,3,4].flatM ap(v=>[v*2]);//[2,4,6,8]
在上方示例中,首先执行了map操作,把数组变换成了[[2],[4],[6],[8]]这样的形式,然后执行flat()操作,把数组扁平化成了[2,4,6,8]。可以看到使用flatMap()可以生成有1层嵌套的中间数组,利用这个特性,在map()时,可以给嵌套的数组增加一些元素,所以结果数组就会多一些元素,如果返回包含空元素的嵌套数组,则结果中就会少一些元素。也就是说,单纯的map()会返回与原数组长度相同的新数组,而flatMap()可以返回与原数组长度不同的新数组。例如,假设有一个包含数字元素的数组,给它里边的每个数字的后面加上该数字的相反数,代码如下:
js
[1,2,3,4].flatM ap(v=>[v,-v]);//[1,-1,2,-2,3,-3,4,-4]
这样在进行map()操作时,对于每个元素,生成了包含两个元素的嵌套数组,即数字本身和它的相反数,即[[1,-1],[2,-2],[3,-3],[4,-4]],最后进行flat()操作时,就把这些数字从嵌套数组中扁平化出来,从而形成了最终结果。
16、解构赋值
解构赋值(Destructuring Assignment)可以用于把数组元素或对象属性拆解出来,分别赋给若干个变量。由于本章重点介绍数组,所以先看数组的解构赋值语法。
对数组进行解构赋值使用[]语法,看起来像是定义一个数组,但是需要把它放在等号的左边,里边需要自行定义变量的名字。例如,假设一个数组保存了一个点的x、y坐标,用解构赋值把它分别赋给x、y两个变量,代码如下:
js
const point=[12,15];
const[x,y]=point; //x=12,y= 15
这里解构赋值会根据位置给变量赋值,x在第1位,所以把数组的第1个元素12赋给了它,同理把15赋给了y。可以看到这样point[0]和point[1]就有了实际的坐标轴的名字x和y。
如果一个数组中有多个元素,但是只想解构前边一部分,后边的自动形成子数组,则这样可以结合rest语法:...符号,给剩下的元素所形成的子数组起一个名字,便于后续引用。rest语法在解构赋值中用法的代码如下:
js
const[a,b,...rest]=[1,10,23,45,32];
a; //1
b; //10
rest; //[23,45,32]
在使用解构赋值把对应的元素赋给变量之后,剩下的元素会自动形成子数组,放到...后边定义的变量中,之后就可以在代码中访问它了。
在解构赋值时,还可以给变量设置默认值,防止对应位置的数组元素是undefined或empty而导致后续访问变量出现错误。给变量设置默认值只需要在变量名后边使用等号加上默认值。设置默认值的代码如下:
js
const[a=10,b=5]=[]
a; //10
b; //5
17、扩展语法
扩展(Spread)语法(或称为展开语法)可以用在需要多个值的地方,这时可以通过一个数组把所需要的值一次性传递进去,然后使用扩展语法把数组扩展成单个的元素,它与rest使用相同的语法:...,但是所做的操作正好相反,rest是把多个元素归集成一个子数组,而spread是把子数组扩展成多个元素。例如,可以使用spread合并两个数组,代码如下:
js
let arr1=[1,2,3];
let arr2=[4,...arr1,6];
arr2; //[4,1,2,3,6]
这里arr1的元素分别被扩展并放到了arr2数组中。另外扩展语法也可以代替concat()连接数组,代码如下:
js
let arr1=[1,2,3];
let arr2=[4,5,6,7];
let arr3=[...arr1,...arr2];
arr3=[1,2,3,4,5,6,7];
函数中可以使用解构赋值的方式,把数组参数拆解成单个变量,这样只需给函数传递一个数组,但是如果一个函数接收多个变量,则可以在传递参数时使用spread语法把数组拆解成多个变量传递进去,代码如下:
js
function add(a,b,c){
console.log(a+b+c);
}
add(...[1,2,3]);//6
可以看到,解构赋值是在函数参数定义的时候使用,而spread则是在调用函数的时候使用。
18、多维数组
JavaScript中没有多维数组的概念,但是能够以嵌套数组的形式定义。例如定义一个2行4列的二维数组,可以用外层数组作为行,用内层数组作为列,代码如下:
js
let twoDim=[
[1,2,3,4],
[5,6,7,8]
]
访问的时候,可以使用连续的[]访问内层的元素。例如访问第2行、第3列的元素7,可以先使用twoDim[1]访问第2个嵌套的数组,然后使用twoDim[1][2]访问内层数组中的第3个元素,结果为7。
在定义多维数组时,要注意内层的数组需要先初始化才能赋值,尤其是在使用循环初始化二维数组的时候,代码如下:
js
let twoDim=[];
for(let i=0;i<3;i++){
for(let j=0;j<3;j++){
twoDim[i][j]= i+ j;//Cannot set property'0'of undefined,不能给undefined设置属性0
}
}
上述代码由于没有初始化twoDim[i]内层数组,所以会提示错误,应该在内层for循环外初始化内层数组,代码如下:
js
for(let i=0;i<3;i++){
twoDim[i]=[]
for(let j=0;j<3;j++){...}
}