文章目录
- 数组
- 数组声明
- at()
- push、pop、shift、unshift
- 栈
- 队列
- 遍历
- length
- .length的意义
- .length可写
- Array()
- 多维数组
- toString()
- 数组比较
- 数组的本质
- 错误的数组使用
- 性能
- 总结
数组
前面讲到的对象虽然是非常强大的工具,但是,我们在编写代码时常常需要处理一些有序数据的集合。在有序集合中,元素的排列是有前后顺序的,例如:文章的列表、章节目录。由于对象并不能提供属性的有序访问,这种情况下,就需要我们使用新的数据结构数组。
数组声明
我们可以通过两种方式创建一个空的数组:
let arr1 = new Array();//方式1let arr2 = [];//方式2
由于第二种方式不仅简单,而且直观,我们常常采用第二种方式。
在声明一个数组时,我们可以直接进行初始化:
let arr = ['Chapter01','Chapter02','Chapter03','Chapter04','Chapter05'];
以上代码创建了一个包含五个元素的数组。
元素访问
我们可以通过下标的方式访问数组元素,需要注意的是,数组的编号是从0
开始的。
let arr = ['Chapter01','Chapter02','Chapter03','Chapter04','Chapter05'];console.log(arr[0]);//访问第一个元素,注意下标是0console.log(arr[3]);console.log(arr[4]);
代码执行结果:
元素替换
通过下标,我们可以直接替换一个数组元素:
let arr = ['Chapter01','Chapter02','Chapter03','Chapter04','Chapter05'];arr[1] = 'Chapter06';//数组变成了['Chapter01','Chapter06',...]
元素添加
通过下标向数组的末尾添加一个元素:
let arr = ['Chapter01','Chapter02']arr[2] = 'Chapter03';//数组变为['Chapter01','Chapter02','Chapter03']
数组长度
通过数组的.length
属性可以获得数组中元素的个数
let arr = ['Chapter01','Chapter02']console.log(arr.length)//2
循环添加
我们可以利用循环迅速创建一个任意长度的数组,这在我们编程中经常用到:
let arr = [];for(let i = 0;i < 10;i++){arr[i] = String(i);}
元素类型
JavaScript
数组不限制元素的种类,在同一个数组中可以存储多种类型的数组:
let arr = [1, '2', { '3' : 3 }];
和对象一样,我们推荐在最后一个元素后添加
,
,这样在添加元素和移动元素的时候会非常容易:let arr = ['chapter1','chapter2','chapter3',]
at()
访问元素的方法并非一种,我们还可以通过at(idx)
方法访问数组的元素:
let arr = ['Chapter01','Chapter02','Chapter03'];console.log(arr.at(2));
代码执行效果如下:
以上代码的执行效果和使用[idx]
方式完全相同,那么使用at
的意义在哪里呢?
最后一个元素
如果我们希望访问数组的最后一个元素,应该怎么办呢?
我们可以使用.length
属性实现:
let arr = ['Chapter01','Chapter02','Chapter03'];console.log(arr[arr.length-1]);
但是这么做非常的不优雅,我们需要写两次数组的名字,此时,我们可以使用at(-1)
访问数组的最后一个元素。
let arr = ['Chapter01','Chapter02','Chapter03'];console.log(arr.at(-1));
同理,访问倒数第二个元素可以使用arr.at(-2)
。
push、pop、shift、unshift
除了直接使用下标访问数组元素,数组还提供了四个方法用于在数组的首尾添加、删除元素:
push
:在数组尾部追加一个元素
let arr = ['First'];arr.push('Second');//此时arr变成了['First','Second']
pop
:在数组的尾部取出一个元素,等同于at(-1)
let arr = ['First','Second','Third'];let last = arr.pop()//等同于arr.at(-1),arr此时为['First','Second']console.log(last);//Third
shift
:从数组头部取出一个元素
let arr = ['First','Second','Third'];let first = arr.shift();//First,arr变为['First','Second','Third']console.log(first);
unshift
:从数组头部插入一个元素
let arr = ['Second'];arr.unshift('First');//arr等于['First','Second']
栈
栈是编程中最常用的线性数据结构,我们可以使用push/pop
方法,把数组当作栈使用。
let st = ['First'];st.push('Second');//压栈st.pop()//出栈
队列
队列是另外一个常用的线性数据结构,我们可以使用push/shift
方法,把数组当作队列使用:
let que = ['First'];que.push('Second');//入队que.shift()//出队
清奇的脑回路
当然,我们可以使用shift/unshift
实现栈,通过unshift/pop
实现队列,只是通常情况下我们都不这么做~~
遍历
最简单的遍历数组的方式是for
循环:
let arr = ['First','Second','Third'];for(let i = 0;i < arr.length; i++){console.log(arr[i])}
代码执行结果:
虽然这么做毫无问题,但是为了更加优雅,我们可以使用for ... of
语法:
let arr = ['First', 'Second', 'Third'];for(let itm of arr){console.log(itm)}
代码执行结果和上面并无差别:
但是这么做也有一个缺点,就是没有办法获得元素下标,所以我们需要在合适的场景下做合适的选择。
length
.length的意义
我们可以使用数组的.length
属性获得数组的长度,但是,实际上数组的.length
属性并非数组里元素的个数,而是数组最大下标的值加一:
let arr = [];arr[996] = 996;console.log(arr.length)//997
代码的执行结果是不是和想象的不太一样:
我们通常情况下不这么使用数组,所以仍然可以使用.length
获取数组的长度。
.length可写
从直观上理解,.length
应该是一个可读的属性,实际上,我们是可以修改.length
属性的值的:
let arr = [1,2,3,4,5,]arr.length = 7;console.log(arr.length)//length = 7console.log(arr[6])//undefinedarr.length = 3;//length = 3,数组被截断arr.length = 5;//length = 5,但是截断的数据不会回来了console.log(arr.length)//5console.log(arr[4])//undefined
代码执行结果如下:
修改length
会产生如下影响:
- 修改后大于当前长度,增长的长度使用
undefined
填充 - 修改后小于当前长度,截断字符串至新长度(截断的部分不可恢复)
Array()
我们使用Array()
同样可以创建一个字符串,不过不常用,因为我们更喜欢[]
语法。
let arr = new Array('First','Second','Third');
Array()
还有一个不讨喜的特性:当我们使用单个数字参数时,会创建一个指定数字长度的空数组!
如果我们正好要创建一个具有单个数字的数组,就会出错。
let arr = new Array(4);console.log(arr.length);//4console.log(arr[3]);//undefined
为了避免出现不必要的错误,还是建议使用[]
,简单又方便。
多维数组
JavaScript
的数组同样可以是多维的:
let arr = [[1,1,1,],[2,2,2,],[3,3,3,],]
toString()
数组的toString()
方法会把数组元素转为字符串,并以,
相隔:
let arr = [1,2,3]console.log(arr.toString())//1,2,3console.log(arr.toString()+1)//1,2,31
数组比较
数组的本质是一个特殊的对象,因此我们不应该使用==
比较两个数组,就像不应该使用==
比较对象一样。
- 仅当两个数组引用的是同一个对象时,它们才相等;
- 如果数组和基础类型比较,将数组转为基础类型后再次比较,转换规则和对象相同对象-基础类型转换;
数组比较:
console.log([] == [])//falseconsole.log([1] == [1])//false
数组与基础类型比较:
console.log([] == 0)//trueconsole.log([] == '0')//falseconsole.log([1,2,3] == "1,2,3")//true
虽然其中有一定的规律,但是不建议使用==
比较数组,我们可以循环逐个比较元素,亦或者使用后面会介绍到的迭代。
数组的本质
数组是一个特殊的对象,方括号加下标的访问方式arr[3]
实际上就是对象的属性访问语法obj[key]
。
数组是对象的扩展,一个属性有序,而且具有length
属性的特殊对象,但是本质上仍然是对象。
我们在最初的文章中曾介绍,JavaScript
共有8
中数据类型,数组属于对象范畴。
如何验证数组是一个对象的本质呢?
实验一,数组变量存储的是引用:
let arr = [1,2,3]let arr2 = arr;console.log(arr2 === arr);//truearr2.push(4)console.log(arr.toString())//1,2,3,4
实验二,给数组添加属性:
let arr = [1,2,3]arr.name = 'arr';
代码执行效果:
但是这么做就会破坏数组的特性,将数组变成一个普通的对象。
错误的数组使用
- 添加非数字属性,例如
arr['name']='xiaoming'
- 越界存储,例如在长为
3
的数组上使用arr[1000]=999
- 倒序填充数组,例如
arr[1000]=1000
,arr[999]=999
如果我们不能把数组当作一个有序的数据结构,可以优先考虑对象。
性能
在数组的末端插入数据比数组的头部插入数据要快,也就是push/pop
速度比shift/unshift
要快。
这是因为,从数组头部移除数据后,引擎会做三件事:
- 移除下标为
0
的值; - 将所有元素像前移动;
- 更新
length
;
数组里面的元素越多,耗费时间越长,push/pop
操作在末尾,不会移动任何元素,所以速度很快。
总结
数组是一个特殊的对象,其元素有序排列,使用下标访问数组元素
两种声明方式:
[]
和new Array()
at(-1)
倒序访问元素push/pop/shift/unshift
操作数组两端的元素把数组用作栈、队列
数组元素遍历
for
、for of
、for in
(不要使用这个)不要使用
==
比较数组