文章目录

  • 数组
    • 数组声明
    • 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

除了直接使用下标访问数组元素,数组还提供了四个方法用于在数组的首尾添加、删除元素:

  1. push:在数组尾部追加一个元素
let arr = ['First'];arr.push('Second');//此时arr变成了['First','Second']
  1. pop:在数组的尾部取出一个元素,等同于at(-1)
let arr = ['First','Second','Third'];let last = arr.pop()//等同于arr.at(-1),arr此时为['First','Second']console.log(last);//Third
  1. shift:从数组头部取出一个元素
let arr = ['First','Second','Third'];let first = arr.shift();//First,arr变为['First','Second','Third']console.log(first);
  1. 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会产生如下影响:

  1. 修改后大于当前长度,增长的长度使用undefined填充
  2. 修改后小于当前长度,截断字符串至新长度(截断的部分不可恢复)

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

数组比较

数组的本质是一个特殊的对象,因此我们不应该使用==比较两个数组,就像不应该使用==比较对象一样。

  1. 仅当两个数组引用的是同一个对象时,它们才相等;
  2. 如果数组和基础类型比较,将数组转为基础类型后再次比较,转换规则和对象相同对象-基础类型转换;

数组比较:

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';

代码执行效果:

但是这么做就会破坏数组的特性,将数组变成一个普通的对象。

错误的数组使用

  1. 添加非数字属性,例如arr['name']='xiaoming'
  2. 越界存储,例如在长为3的数组上使用arr[1000]=999
  3. 倒序填充数组,例如arr[1000]=1000arr[999]=999

如果我们不能把数组当作一个有序的数据结构,可以优先考虑对象。

性能

在数组的末端插入数据比数组的头部插入数据要快,也就是push/pop速度比shift/unshift要快。

这是因为,从数组头部移除数据后,引擎会做三件事:

  1. 移除下标为0的值;
  2. 将所有元素像前移动;
  3. 更新length;

数组里面的元素越多,耗费时间越长,push/pop操作在末尾,不会移动任何元素,所以速度很快。

总结

  1. 数组是一个特殊的对象,其元素有序排列,使用下标访问数组元素

  2. 两种声明方式:[]new Array()

  3. at(-1)倒序访问元素

  4. push/pop/shift/unshift操作数组两端的元素

  5. 把数组用作栈、队列

  6. 数组元素遍历forfor offor in(不要使用这个)

  7. 不要使用==比较数组