本文深入解析 JavaScript 数组的核心特性,涵盖构造函数与字面量两种创建方式的区别及注意事项。详细阐述数组索引作为对象属性的特殊性、length 属性的动态调整机制,以及 push/pop、shift/unshift 等栈队列操作。重点讲解 splice 方法在增删改中的应用,并剖析 sort 方法默认排序行为与自定义排序函数的实现原理。
JavaScript 数组概述
在程序语言中,数组的重要性不言而喻。作为 JavaScript 中最常用的对象之一,数组本质上是值的有序集合。得益于 JavaScript 的弱类型特性,数组具有极高的灵活性与强大功能:与 Java 等强类型语言中数组仅能存储同一类型或其子类型元素不同,JavaScript 允许在同一个数组中混合存储多种类型的元素。此外,数组长度并非固定不变,而是可以动态调整,能够根据数据的增加或减少自动扩展或收缩。
数组的创建方式
创建数组
在 JavaScript 中,有多种方式可以创建数组。
构造函数
1. 无参构造函数,用于创建一个空数组。
代码如下:
var a1=new Array();
2. 使用一个数字参数作为构造函数参数,指定数组长度(由于数组长度本身可动态调整,此用法的实际意义有限),从而创建指定长度的数组。
代码如下:
var a2=new Array(5);
3. 带有初始化数据的构造函数,用于创建数组并传入初始化参数。
代码如下:
var a3=new Array(4,'hello',new Date());
字面量
1. 使用方括号创建空数组,其效果等同于调用无参构造函数。
代码如下:
var a4=[];
2. 使用中括号并传入初始化数据,其效果等同于调用带有初始化数据的构造函数。
代码如下:
var a5=[10];
注意点
1. 在使用构造函数创建数组时,如果仅传入一个数字参数,则会创建一个长度为该数字的数组;如果传入多个参数,则创建一个数组,并将这些参数作为初始化数据存入。
代码如下:
var a1=new Array(5);
console.log(a1.length);//5
console.log(a1); //[] ,数组是空的
var a2=new Array(5,6);
console.log(a2.length);//2
console.log(a2); //[5,6]
然而,使用字面量方式创建数组时,无论传入多少个参数,所有参数都会被当作初始化内容处理。
代码如下:
var a1=[5];
console.log(a1.length);//1
console.log(a1); //[5]
var a2=[5,6];
console.log(a2.length);//2
console.log(a2); //[5,6]
2. 在使用带初始化参数的方式创建数组时,最好避免在末尾添加多余的逗号。不同浏览器对此的处理方式存在差异。
代码如下:
var a1=[1,2,3,];
console.log(a1.length);
console.log(a1);
这段脚本在现代浏览器上运行的结果符合预期,长度为 3;但在低版本 IE 浏览器中,数组长度可能为 4,且最后一条数据为 undefined。
数组索引与长度机制
数组的索引与长度 数组的值可以通过自然数索引进行读写操作,下标也可以是计算出非负整数的变量或表达式。
代码如下:
var a1=[1,2,3,4];
console.log(a1[0]); //1
var i=1;
console.log(a1[i]); //2
console.log(a1[++i]); //3
数组本质上也是对象,我们可以利用索引访问数据的奥秘在于:数组会将索引值转换为对应的字符串(例如 1 转换为 "1")作为对象属性名。
console.log(1 in a1);//true,确实是一个属性 索引的特殊性在于数组会自动更新 length 属性。当然,由于 JavaScript 语法规定数字不能作为变量名,因此我们不能使用 array.1 这样的格式。由此可见,负数甚至非数字的“索引”也是允许的,只不过这些会变为数组的属性,而非真正的索引。
代码如下:
var a=new Array(1,2,3);
a[-10]="a[-10]";
a["sss"]="sss";
从上述示例可以看出,所有的索引实际上都是属性名,但只有自然数(且有最大值限制)才被视为索引。通常在使用数组时不会出现越界错误,正是因为此机制。数组的索引可以不连续,当访问不存在的索引元素时,返回 undefined。
代码如下:
var a=new Array(1,2,3);
a[100]=100;
console.log(a.length); //101
console.log(a[3]); //undefined
console.log(a[99]); //undefined
console.log(a[100]); 100

上述例子中,虽然直接对 a[100] 赋值不会影响 a[4] 或 a[99],但数组长度却受到影响。数组 length 属性等于数组中最大索引值加 1。我们知道 length 属性是可写的,当强制将 length 设置为小于或等于最大索引值时,数组会自动删除索引大于或等于新 length 的数据。在刚才的代码中追加几句:
代码如下:
a.length=2
console.log(a);//[1,2]
此时会发现 a[2] 和 a[100] 被自动删除了。同理,如果把 length 设置为大于最大索引加 1 的值时,数组会自动扩张,但不会添加新元素,只是在尾部追加空空间。
代码如下:
a.length=5;
console.log(a); //[1,2] //后面没有3个undefined
数组元素的增删操作
元素添加/删除 基本方法
上面的例子已经展示了向数组内添加元素的方法,直接使用索引即可(索引无需连续)。
代码如下:
var a=new Array(1,2,3);
a[3]=4;
console.log(a);//[1, 2, 3, 4]
前文提到数组也是对象,索引只是特殊的属性,因此我们可以使用删除对象属性的方法,即使用 delete 删除数组元素。
代码如下:
delete a[2];
console.log(a[2]); //undefined
这与直接把 a[2] 赋值为 undefined 类似,不会改变数组长度,也不会改变其他数据的索引和值对应关系。

栈方法
上面的例子中,有同学可能发现了,尤其是删除方法,并不是我们期望的表现形式。很多时候,我们希望删除中间一个元素后,后续元素的索引都自动减一,数组 length 同时减一,就像从堆栈中取出一个元素。数组已经帮我们做好了这种操作方式,pop 和 push 能够让我们像使用堆栈那样,以先入后出的方式使用数组。
代码如下:
var a=new Array(1,2,3);
a.push(4);
console.log(a);//[1, 2, 3, 4]
console.log(a.length);//4
console.log(a.pop(a));//4
console.log(a); //[1, 2, 3]
console.log(a.length);//3
队列方法
既然栈方法都实现了,先入先出的队列怎么能少。shift 方法可以删除数组索引最小的元素,并使后续元素的索引都减一,length 也减一。这样使用 shift/push 就可以模拟队列了。当然,与 shift 方法对应的有一个 unshift 方法,用于向数组头部添加一个元素。
代码如下:
var a=new Array(1,2,3);
a.unshift(4);
console.log(a);//[4, 1, 2, 3]
console.log(a.length);//4
console.log(a.shift(a));//4
console.log(a); //[1, 2, 3]
console.log(a.length);//3
splice 方法详解
终极神器
JavaScript 提供了一个 splice 方法,用于一次性解决数组添加、删除(这两种方法结合即可达到替换效果)。该方法有三个参数:
1. 开始索引
2. 删除元素的位移
3. 插入的新元素,当然也可以写多个
splice 方法返回一个由删除元素组成的新数组,如果没有删除操作,则返回空数组。
代码如下:
var a=new Array(1,2,3,4,5);
删除
指定前两个参数,可以使用 splice 删除数组元素,同样会带来索引调整及 length 调整。
代码如下:
var a=new Array(1,2,3,4,5);
console.log(a.splice(1,3));//[2, 3, 4]
console.log(a.length);//2
console.log(a);//[1,5]
如果数组索引不是从 0 开始的,那么结果会很有意思。有一个这样的数组:
代码如下:
var a=new Array();
a[2]=2;
a[3]=3;
a[7]=4;
a[8]=5;

代码如下:
console.log(a.splice(3,4)); //[3]
console.log(a.length); //5
console.log(a); //[2: 2, 3: 4, 4: 5]
从上面的例子可以看到,splice 的第一个参数是绝对索引值,而不是相对于数组索引;第二个参数并不是删除元素的个数,而是删除动作执行的次数,并非按数组实际索引移动,而是连续移动。同时调整后面元素索引,前面索引不理会。
插入与替换
只要将方法第二个参数(即删除动作执行的次数)设为 0,第三个参数及以后填写要插入内容,splice 就能执行插入操作;而如果第二个参数不为 0,则变成了先在该位置删除再插入,也就是替换效果。
代码如下:
var a=new Array(1,2,3,4,5);
a.splice(1,0,9,99,999);
console.log(a.length); //8
console.log(a);//[1, 9, 99, 999, 2, 3, 4, 5]
a.splice(1,3,8,88,888);
console.log(a.length);//8
console.log(a);//[1, 8, 88, 888, 2, 3, 4, 5]常用方法
其他常用数组方法
join(char)
这个方法在 C# 等语言中也有,作用是把数组元素(对象调用其 toString() 方法)使用参数作为连接符连接成一个字符串。
代码如下:
var a=new Array(1,2,3,4,5);
console.log(a.join(',')); //1,2,3,4,5
console.log(a.join(' ')); //1 2 3 4 5
slice(start,end)
不要和 splice 方法混淆,slice
代码如下:
var a=new Array(1,2,3,4,5);
console.log(a); //[1, 2, 3, 4, 5]
console.log(a.slice(1,2));//
console.log(a.slice(1,-1));//[2, 3, 4]
console.log(a.slice(3,2));//[]
console.log(a); //[1, 2, 3, 4, 5]该方法用于返回数组中一个片段或子数组。如果只写一个参数,返回从该参数到数组结束的部分;如果参数出现负数,则从数组尾部计数(-3 意思是数组倒第三个,一般人不会这么干,但是在不知道数组长度,想舍弃后 n 个的时候有些用,不过数组长度很好知道。。。。,好纠结的用法)。如果 start 大于 end 返回空数组。值得注意的一点是 slice 不会改变原数组,而是返回一个新的数组。
concat(array)
看起来像是剪切,但这个真不是形声字。concat 方法用于拼接数组,a.concat(b) 返回一个由 a 和 b 共同组成的新数组,同样不会修改任何一个原始数组,也不会递归连接数组内部数组。
代码如下:
var a=new Array(1,2,3,4,5);
var b=new Array(6,7,8,9);
console.log(a.concat(b));//[1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(a); //[1, 2, 3, 4, 5]
console.log(b); //[6, 7, 8, 9]
reverse()
该方法用于将数组逆序,与之前不同的是它会修改原数组。
代码如下:
var a=new Array(1,2,3,4,5);
a.reverse();
console.log(a); //[5, 4, 3, 2, 1]
同样,当数组索引不是连续或以 0 开始,结果需要注意。
代码如下:
var a=new Array();
a[2]=2;
a[3]=3;
a[7]=4;
a[8]=5;

代码如下:
a.reverse();
sort
sort 方法用于对数组进行排序。当没有参数时会按字母表升序排序,如果含有 undefined 会被排到最后面,对象元素则会调用其 toString 方法。如果想按照自己定义的方式排序,可以传一个排序方法进去,很典型的策略模式,同样 sort 会改变原数组。
代码如下:
var a=new Array(5,4,3,2,1);
a.sort();
console.log(a);//[1, 2, 3, 4, 5]
但是。。。
代码如下:
var a=new Array(7,8,9,10,11);
a.sort();
console.log(a);//[10, 11, 7, 8, 9]
因为按照字母表排序,7 就比 10 大了,这时候我们需要传入自定义排序函数。
代码如下:
var a=new Array(7,8,9,10,11);
a.sort(function(v1,v2){
return v1-v2;
});
console.log(a);//[7, 8, 9, 10, 11]
原理和 C# 中的 sort 类似(.NET Framework 中的设计模式——应用策略模式为 List 排序),只不过可以直接传递方法进去。以下内容纯属猜测。
sort 内部使用快速排序,每次比较两个元素大小的时候,如果没有参数,则直接判断字母表;如果有参数,则把正在比较的两个参数传入自定义方法并调用(正在比较的两个数会传给自定义方法的 v1、v2)。如果返回值大于 0 表示 v1>v2,如果等于 0,表示 v1=v2,如果小于 0,表示 v1<v2。其实我们传入的方法就是告诉 sort 怎么比较两个元素谁大谁小,至于排序移动元素的过程人家写好了,猜测结束。
最后 了解了这些,看看数组真的很了不得啊,既强大又灵活。但是在遍历元素及获取元素位置等方面也存在一定的不便,这些在 ECMAScript 中已经得到解决。熟练使用可以让我们的 JavaScript 代码优雅而高效。