JavaScript数组¶
约 5193 个字 496 行代码 预计阅读时间 24 分钟
介绍¶
数组也是一种复合数据类型,在数组可以存储多个不同类型的数据。与C++或者JavaScript一样,JavaScript数组中的每个数据都有一个唯一的索引,所以可以通过索引进行数据的访问。在数组中,数组的数据也被称为元素,对应的索引最小值为0
在JavaScript中,有两种创建数组的方式:
- 使用
Array()
构造函数创建一个空数组,这种方式无法在创建数组时为数组添加元素 - 使用
[]
创建一个空数组,这种方式可以在初始化时为数组添加元素
例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 |
|
与C++和Java一样,可以直接通过下标访问和修改元素,例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 |
|
在JavaScript中,因为数组也是对象,所以数组也有自己的属性,这里先介绍数组常用的属性:长度。在JavaScript中,如果想获取数组的长度,只需要访问数组对象的length
属性即可,例如下面的代码:
JavaScript | |
---|---|
1 2 3 |
|
JavaScript数组中的length
并不是一个常量,其长度可以指定,如果指定的数值大于原先的数值,则相当于为数组扩容,多余的部分以undefined
填充;如果指定的数值小于原先的数值,则相当于删除数组中的元素。例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 7 |
|
Note
需要注意,尽管length
属性可以修改,但是其值不可以是负数,否则会报错RangeError: Invalid array length
遍历数组¶
基本使用¶
和C++与Java一样,使用for
循环枚举下标结合数组的长度即可,这种方法不再赘述。在JavaScript中,还有一种遍历数组的方式是通过for-of
语句
在JavaScript中,for-of
可以相当于C++和Java中的增强for
,其语法如下:
JavaScript | |
---|---|
1 2 3 4 5 |
|
例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
for-in
和for-of
的区别¶
for-in
用于遍历对象的可枚举属性(包括原型链上的属性),遍历的结果是对象的key
而不是value
,可以用于普通对象和数组,但是因为数组遍历出来的是下标,所以基本使用for-in
遍历数组,例如:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
for-of
用于遍历可迭代对象(如数组、字符串、Map、Set 等)的值,遍历的结果是对象的值(value),而不是键(key),适合遍历数组、字符串等实现了[Symbol.iterator]
方法的对象,例如:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
需要注意,使用{}
直接定义的普通对象默认不可以迭代,所以不能使用for-of
遍历
数组常用方法¶
isArray()
方法¶
Array类的静态方法,参数传递一个数组对象,用于检查某一个对象是否是数组。方法返回true
或者false
JavaScript | |
---|---|
1 2 3 4 |
|
at()
方法¶
Array对象方法,参数传递索引。可以根据索引获取数组中的指定元素,与使用[]
访问的方式不同,at()
可以接收负索引作为参数,如果是负数索引,就相当于从后向前遍历。方法返回下标对应的值
JavaScript | |
---|---|
1 2 3 4 |
|
concat()
方法¶
Array对象方法,其参数传递一个或多个数组对象,用来连接调用方法的数组对象和参数中的数组对象。方法返回一个新数组
Note
需要注意的是,这个方法会将连接后的数组返回,并不会修改原先任何一个对象数组
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
indexOf()
方法¶
Array对象方法,用于获取参数元素在数组中第一次出现的索引,该函数接受两个参数,第一个参数代表要查找的元素,第二个参数代表查找的起始位置。方法找到对应的数值,返回其下标,否则返回-1
Note
第二个参数可以不传递,默认情况下从数组第一个元素的位置开始查找
JavaScript | |
---|---|
1 2 3 4 5 |
|
lastIndexOf()
方法¶
本方法与indexOf()
方法功能类似,只是其是从后向前查找,不再演示。方法接受两个参数,第一个参数表示查找元素,第二个表示起始位置,找到对应的数值,返回其下标,否则返回-1
join()
方法¶
Array对象方法,其作用是将数组中的元素根据连接符连接成一个字符串,方法接受一个参数,参数表示连接符,默认为逗号,
JavaScript | |
---|---|
1 2 3 4 |
|
slice()
方法¶
Array对象方法,其有连两个参数,第一个参数表示截取的起始位置(包括该位置),第二个参数表示截取的结束位置(不包括该位置)。方法返回一个新数组
- 如果指定了起始位置和终止位置,那么函数就会从起始位置一直截取到终止位置,不包括终止位置
- 如果指定了起始位置,但是没有指定终止位置,则默认情况下截取到数组的最后一个元素,包括最后一个元素
- 如果两个参数都不指定,则默认情况下对数组进行浅拷贝
Note
需要注意,不论是截取还是浅拷贝,都不会改变原来的数组
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
浅拷贝与深拷贝¶
在JavaScript中,浅拷贝意味着对数组或者对象进行浅层次的拷贝。在数组中,如果其元素都是原始值,则浅拷贝与深拷贝没有差异,但是如果数组中是对象,则会出现问题
数组和对象的深浅拷贝概念是一致的,下面以数组的深浅拷贝进行叙述:
- 浅拷贝:如果数组中存储了对象,则浅拷贝情况下只会拷贝对象本身,但是不会单独拷贝对象中的属性。如果使用浅拷贝将数组中的对象拷贝给另外一个对象,则当另外一个对象修改其属性时,被拷贝的对象中的属性也会同时改变
- 深拷贝:与浅拷贝相反,如果数组中存储了对象,则深拷贝情况下不仅会拷贝对象本身,也会将对象中的属性一起拷贝。如果使用深拷贝将数组中的对象拷贝给另外一个对象,则当另外一个对象修改其属性时,被拷贝的对象中的属性不会受到影响
在JavaScript中,如果想对一个对象进行深拷贝,可以调用structuredClone()
函数,其参数传递被拷贝的对象,该函数会返回一个新对象。如果想对一个对象进行浅拷贝,可以调用Object类的静态方法assign()
,其参数传递被拷贝的对象,该函数会返回一个新对象
例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
|
需要注意,在上面的代码中,obj
对象自身中的name
属性和age
属性都是原始值,所以深浅拷贝效果都是一样的,但是其中的friend属性属于引用类型,此时浅拷贝就会出现修改新对象影响原对象的问题
对象的复制¶
在JavaScript中,经常会涉及到对象与对象直接的赋值,下面是常见的两种复制方式:
- 使用展开运算符
...
- 调用Object类中的静态方法
assign()
对于第二种方法,在浅拷贝和深拷贝处已经介绍,此处不再赘述。下面讨论展开运算符...
在JavaScript中,展开运算符可以将一个数组中的元素展开到另一个数组中或者作为函数的参数传递,也可以通过它也可以对数组进行浅拷贝
例如下面的代码
JavaScript | |
---|---|
1 2 3 |
|
利用展开运算符将数组中的元素传递给函数的参数,代码如下:
JavaScript | |
---|---|
1 2 3 4 5 6 7 |
|
push()
方法¶
Array对象方法,向数组的末尾添加一个或多个元素,参数传递一个或多个值,并返回新的长度
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
pop()
方法¶
Array对象方法,删除并返回数组的最后一个元素
JavaScript | |
---|---|
1 2 3 4 5 |
|
unshift()
方法¶
Array对象方法,向数组的开头添加一个或多个元素,参数传递一个或多个值,并返回新的长度
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
shift()
方法¶
Array对象方法,删除并返回数组的第一个元素
JavaScript | |
---|---|
1 2 3 4 5 |
|
splice()
方法¶
Array对象方法,其作用由其传递的参数传递,一共由三种常用的作用:
- 删除元素
- 插入元素
- 替换数组中的元素
该方法参数一共有三个:
- 第一个参数:删除的起始位置
- 第二个参数:删除元素的数量,包括起始位置的元素
- 第三个参数:要插入的元素
如果只传递第一个参数,那么会从该位置开始一直删除到最后一个元素之间的所有元素;如果传递第一个和第二个参数,则代表删除从指定位置开始的指定个数的元素;如果传递第一个参数,并且第二个参数为1,则表示删除指定位置的元素;如果传递第一个参数、第二个参数和第三个参数,则代表删除指定位置开始的指定个数的元素,并在起始位置处添加第三个参数的元素
该方法会返回删除的元素
Note
第三个参数可以是多个,而不仅仅局限于一个
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
reverse()
方法¶
Array对象方法,其作用是反转数组,方法返回一个新数组
JavaScript | |
---|---|
1 2 3 4 5 |
|
回调函数的应用¶
高阶函数:如果一个函数的参数或返回值是函数,则这个函数就称为高阶函数。如果将函数作为函数的参数,则意味着可以对另一个函数动态地传递代码
例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
在实际开发中,一般不会直接使用一个定义的函数作为实参传递,更多地将箭头函数作为实参传递给函数的形参,例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 |
|
上面演示了回调函数作为参数传递,下面演示函数作为返回值的例子:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
在上面的代码中,首先fn()
函数接收一个箭头函数作为参数,进入fn()
函数,先执行打印this is fn function
,接着执行return
语句,因为return
语句返回的是一个函数,所以在外部使用func
变量接收,此时func
就执行fn
函数返回的函数,调用func()
函数,先打印this is answer function
,再执行了cb
指向的函数(也就是fn
的回调函数参数:()=>{console.log("this is parameter"); return "this is result";}
),打印了this is parameter
并返回this is result
,func()
函数执行完后,将其返回值给result
变量,func()
函数内部的return
语句返回result
后在调用处被str
接收,打印str
就是this is result
闭包¶
介绍¶
在JavaScript中,闭包表示能访问到外部函数作用域中变量的函数,一般在需要隐藏一些不希望被外部访问的内容时就可以使用闭包
形成闭包的三个条件如下:
- 函数的嵌套
- 内部函数要引用外部函数中的变量
- 内部函数要作为返回值返回
例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
闭包的原理¶
在JavaScript中,函数的作用域在函数创建的时候就已经确定好了,所以函数内访问某一个变量关键并不是是看函数调用的位置之前是否存在对应的变量,即与其实际的调用位置没有关系,这一作用域也被称为词法作用域,而闭包就是利用词法作用域
例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
在上面的代码中,因为函数fn()
在创建时就已经确定好了作用域,所以其访问到的a
一定是全局变量的a
,而不是调用位置上面的a
接着看闭包下的情况:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
因为fn4()
函数创建时的作用域在fn3
内部,所以访问的变量是fn3()
函数内部的a
,而不是全局变量的a
闭包的注意事项¶
闭包的声明周期:
- 闭包在外部函数调用时产生,外部函数每次调用都会产生一个全新的闭包
- 在内部函数丢失时销毁(内部函数被垃圾回收了,闭包才会消失)
例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
因为闭包主要用来隐藏一些不希望被外部访问的内容,所以闭包也需要占用一定的内存空间。在实际开发中,相较于类来说,闭包比较浪费内存空间(类可以使用原型而闭包不能),如果需要执行次数较少时,使用闭包,否则使用类
数组高阶方法¶
sort()
方法¶
Array对象方法,其作用是为数组中的数据排序,但是默认情况下,排序的比较方式是按照元素的Unicode编码进行比较,所以如果数组中存储的都是数值,则此时会出现10和1紧挨着的类似情况
例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
为了解决这种问题,就需要调用者自己传递一个回调函数告诉sort
函数排序时元素的比较方式,在JavaScript中,传递给sort函数的回调函数接收两个参数,这两个参数分别代表第一个比较操作数和第二个比较操作数,假设二者分别为a
和b
,则有:
- 当
a - b > 0
,则表示升序排序 - 当
a - b < 0
,则表示降序排序 - 当
a - b === 0
,则表示不改变原始顺序
Note
因为只有a - b === 1
时,函数才会进行排序,所以如果需要降序,就需要写成b - a
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
forEach()
方法¶
Array对象方法,其主要作用是遍历数组元素,其参数传递一个回调函数,回调函数一共有三个参数:
- 第一个参数:
element
,表示当前的元素 - 第二个参数:
index
,表示当前元素的索引 - 第三个参数:
array
,表示遍历的数组对象
Note
尽管可以传递三个参数,但是实际上一般只会使用到第一个参数
例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
快速遍历对象¶
前面遍历对象时使用的是for-in
,但是这样遍历对象有点繁琐,所以可以考虑使用Object.keys()
方法结合forEach
快速遍历对象。其中,Object.keys()
方法会返回对象中所有的key
构成的数组,例如:
JavaScript | |
---|---|
1 2 3 4 5 6 |
|
此时就可以使用数组的forEach
方法实现快速遍历:
JavaScript | |
---|---|
1 2 3 4 5 6 |
|
filter()
方法¶
Array对象方法,其主要作用是根据某一个条件筛选元素,其参数传递一个回调函数,回调函数一共有三个参数:
- 第一个参数:
element
,表示当前的元素 - 第二个参数:
index
,表示当前元素的索引 - 第三个参数:
array
,表示遍历的数组对象
该方法会返回一个新数组,不会修改原始数组中的数据
Note
尽管可以传递三个参数,但是实际上一般只会使用到第一个参数
JavaScript | |
---|---|
1 2 3 4 |
|
map()
方法¶
Array对象方法,根据当前数组生成一个新数组,其参数为一个回调函数,回调函数一共有三个参数:
- 第一个参数:
element
,表示当前的元素 - 第二个参数:
index
,表示当前元素的索引 - 第三个参数:
array
,表示遍历的数组对象
该方法会返回一个新数组,不会修改原始数组中的数据
Note
尽管可以传递三个参数,但是实际上一般只会使用到第一个参数
JavaScript | |
---|---|
1 2 3 4 |
|
includes()
方法¶
Array对象方法,用于判断数组是否存在某种元素,该函数有两个参数,第一个参数表示查询元素,第二个参数表示查询起始点(默认从0开始),如果第二个参数不是整数,会将其转换成整数
JavaScript | |
---|---|
1 2 3 4 5 6 7 |
|
every()
方法¶
Array对象方法,该方法用于检测数组中的每一个元素是否符合给定条件,一旦检测到不符合就退出。类似于对数组所有的元素进行逻辑与。其参数为一个回调函数,回调函数一共有三个参数:
- 第一个参数:
element
,表示当前的元素 - 第二个参数:
index
,表示当前元素的索引 - 第三个参数:
array
,表示遍历的数组对象
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
some()
方法¶
Array对象方法,用于检测数组中的每一个元素是否至少有一个符合给定条件,一旦有就返回true,否则返回fale。类似于对于数组所有的元素进行逻辑或。其参数为一个回调函数,回调函数一共有三个参数:
- 第一个参数:
element
,表示当前的元素 - 第二个参数:
index
,表示当前元素的索引 - 第三个参数:
array
,表示遍历的数组对象
JavaScript | |
---|---|
1 2 3 4 |
|
reduce()
方法¶
Array对象方法,可以用来将一个数组中的所有元素整合为一个值,其参数有两个,第一个为一个回调函数,回调函数一共有两个参数:
- 第一个参数:表示用于合并的第一个值
- 第二个参数:表示用于合并的第二个值
Note
尽管可以传递三个参数,但是实际上一般只会使用到第一个参数
方法的第二个参数为初始值,方法返回一个合并后的值
Note
map
和reduce
的区别:map
用于对每个元素进行计算,reduce
用于对整个数组的元素进行累计计算
JavaScript | |
---|---|
1 2 3 4 5 |
|
可变参数¶
继this
之后,arguments
参数是第二个函数隐含的参数。在JavaScript中,arguments
是一个类数组对象(伪数组),所谓伪数组,就是和数组相似,可以通过索引来读取元素,也可以通过for
循环变量,但是它不是一个数组对象,不能调用数组的方法
arguments
用来存储函数的实参,无论用户是否定义形参,传递给函数的参数值都会存储到arguments
对象中,所以可以通过该对象直接访问实参
例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
但是,尽管有arguments
参数,但是其不能使用数组的方法,所以在实际使用中很少使用。在前面介绍了展开运算符...
可以将数组的元素展开依次传递给函数,此时展开运算符用作函数的实参,实际上也可以用作函数的形参部分,此时这个参数称为该函数的可变参数
在JavaScript中,可变参数可以接收任意数量实参,并将他们统一存储到一个数组中返回,可变参数的作用和arguments
基本是一致,但是也具有一些不同点:
- 可变参数的名字可以自己指定(即形参名)
- 可变参数就是一个数组,可以直接使用数组的方法
- 可变参数可以配合其他参数一起使用
所以上面的代码可以改为:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
需要注意,因为可变参数可以与普通参数一起使用,所以一旦一起使用,与Java的可变参数一样,可变参数必须作为最后一个参数,此处不再演示
函数的方法¶
在JavaScript中,函数也是一个对象,所以其也有对应的方法,下面介绍其中的方法
call()
和apply()
方法¶
call()
和apply()
方法,二者的共同点都是调用函数,并且可以使用第一个参数指定函数的this
,但是对于call()
函数来说,第一个参数之后的参数只需要一个一个列出来就会作为函数的实参传递,而对于apply()
函数来说,第一个参数之后的参数需要放在一个数组中后再传递给函数,例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
bind()
方法¶
bind()
是函数的方法,可以用来创建一个新的函数,其主要作用如下:
bind()
可以为新函数绑定this
bind()
可以为新函数绑定参数值
该方法会返回绑定的新函数
Note
bind()
方法会为新函数绑定this
和参数值,所以一旦绑定,不论新函数之后是调用call()
方法还是apply()
方法都不能再修改其中的this
和参数值
例如下面的代码:
JavaScript | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|