数组对于一个编程语言而言可谓举足轻重,当然 JavaScript
也对其相当重视,下面我就将自己接触到的数组有关的属性和方法记录下来。此文章会不断完善,以供自己或他人查看。
简单介绍
有两种方式声明一个数组,如下所示
var arrayOne = new Array();
var arrayTwo = [];
按理说,以上两种方式声明的数组功能都是相同的,但是不建议使用第一种方式,准确的说,为了可读性以及代码的执行速度,我们选择第二种方式。
虽然第一种方式不建议使用,但为了给其面子还是来介绍一下它的使用方法。Array
有三个构造方法。
-
默认的无参构造方法
var array = new Array();
这种方式声明的数组默认数组长度为 0 。
-
带有数组元素的构造方法
var array = new Array(1, 2, 3, 4);
-
指定数组长度的构造方法
var array = new Array(5);
数组的长度需要设定在 0 - 2^32 -1 之间的一个整数, 如果不是, 将会抛出 RangeError
异常。在事先不知道数组长度的情况下还是不要盲目使用这个方法。举个例子来说,当前我们指定数组的长度为 5,如果我们放进数组 10 个元素,数组的长度会自动增加到 10 。而如果我们只放进数组 1 个元素,那么其它的 4 个位置不就虚位以待了吗? 这在无形中也就浪费了我们的内存资源。
也许你已经看到了,上面介绍 2,3 两种 Array
构造方法存在冲突,如果我们指定如下代码,浏览器将会如何解释我们的代码呢?
var array = new Array(1);
其实以上代码会被默认解释为一个长度为 1 的空数组,如果你真的想将传进去的一个值作为数组中的元素,那么你只能将这个值当做字符串传进去了。
var array = new Array("1");
当然,我相信大部分人也不会用 new Array()
去声明一个数组,那么自然也不会有这样的问题存在了。
在 JavaScript
中, 数组被当做是一个对象,我们可以用以下代码去检测。
console.log(typeof(array)); // return object
既然如此,那么问题来了。 对于一个已知变量,我们怎么判断它是不是数组呢?显然我们不可以利用 typeof
了。有三种方法可以鉴别,下面来一一介绍。
在
ECMAScript5
中有一个新方法Array.isArray()
,这个方法可以帮主我们鉴别变量是否为数组,如果是数组,返回true
, 否则返回false
。 但是这种方法存在一个问题,那就是老的浏览器并不支持ECMAScript5
。-
为了解决上一个方法中出现的问题,我们可以自己定义一个
isArray()
方法。function isArray(x) { return x.constructor .toString() .indexOf("Array") > -1; }
-
第三种方法需要借助
instanceof
关键词,这个方法可以确定一个对象是否由某一个构造函数所创建。array instanceof Array
在使用数组的时候需要尤其注意,因为数组是通过下标来操作元素的,比如 a[0],a[1] ...
。但是有些时候由于错误的操作,极可能引发不可预知的问题,就像下面这样。
var person = [];
person["name"] = "hwaphon";
person["age"] = 20;
可见,我们声明 person
为一个数组,却以操作对象的方式操作它,这个时候 JavaScript
会自动将其重定位为一个对象,这也就意味着数组当中的方法可能就失效了。比如说,这个时候调用 person.length
会返回 0 。但是值得注意的是,由于在重定位之前 person
是利用数组的构造函数产生的,所以这个时候如果你利用 Array.isArray()
去检测 person
会发现仍然返回 true
。
方法
说到数组,那么我们的本心就是利用数组来存储一组数据,那么也就面临了问题,我们该如何存取数据呢?
首先,用如下代码声明一个数组。
var array = [];
可以利用如下代码一直往数组的末尾添加一个元素。
array[array.length] = element;
至于如何获取数组中的值,可以直接指定数组的下标,比如说 a[0]
(如果这个元素尚没有指定值,将会返回 undefined
)。
存取元素都解决了,还需要面对的一个问题是如何删除元素,或许我们可以通过下面这段代码删除最后一个元素(至于其它位置的元素,只需改变 i
的值即可)
for (var i = 0; i < array.length; i++) {
array[i] = array[i + 1];
}
恩,看上去是这么回事,而且最后一个元素会被赋值为 undefined
,非常完美。可是当调用 array.length
的时候发现数组的长度并没有改变,因为这样我们只是将值给删除了而已。没关系,还有办法,我们在重新声明一个数组,将当前数组非 undefined
的值复制进去就是了。哎,好像又需要写一个函数专门去做这件事了。体贴的 JavaScript
设计者自然也想到了这一点,我们可以使用其内置的方法去解决删除的问题,比如 pop(), shift(), splice()
。下面来探讨一下这些方法的使用。
我们可以使用 push(), unshift()
方法在数组中插入元素,二者的区别在于 push()
方法会将元素插在数组的末尾,unshift()
方法将元素插在数组的开头。
array.unshift(0);
array.push(5);
使用 pop(), shift()
方法在数组中删除元素,二者的区别在于 pop()
删除数组的末尾元素,shift()
方法删除数组的起始元素。
array.pop();
array.shift();
可见,我们可以使用 push() 和 shift()
模仿队列的工作方式。
还有一个比较强大的方法,那就是 splice()
方法,我们可以利用它删除任何位置的任何长度元素,也可以替换掉数组中的一部分元素。
array.splice(1,2);
以上代码的意思是,从数组下标 1 开始,连续删除两个元素。
array.splice(1,0,5,6,7,8,9);
以上代码的意思是从数组下标 1 开始,删除 0 个元素,并且将 5,6,7,8,9 五个元素插入到数组当中去,而且是插入在下标 1 的后面。
-
数组复制
使用slice(start, end)
方法,可以完成对一个数组的复制,当然可以选择性复制。var fullCopy = array.slice(); var partCopy = array.slice(1,3);
上面的第一种方法,是将 array
这个数组中的所有元素复制下来。 第二种方法指定了起始和终止下标,所以复制的是 array
中数组下标 1-3 的元素。
-
数组合并
var arrayOne = [0, 1, 2]; var number = 3; var arrayTwo = [4, 5, 6]; var resultArray = arrayOne.concat(number, arrayTwo);
concat()
中的参数可指定多个,不过要注意先后顺序。
- 迭代器函数
JavaScript
内置了很多迭代器函数,下面来一一介绍。
every(callback, thisArg)
方法会迭代数组中的每一个元素,直到有一个元素返回了 false
。这个方法有两个参数,第一个参数是回调函数,而且该回调函数中还包含了三个参数,分别是 currentValue, index, arrayObj
。第二个参数 thisArg
是可选的,用于指定回调函数中的 this
指针指向何处,如果不指定,则 this
指针为 undefined
。这个方法有什么用呢?我们可以用于判断一组数据中的所有元素是否满足同一条件。比如说,我们用下面这个数组保存了 一部分人的年龄信息。
var personAge = [18,25,102,-6];
我们可以用 every()
函数判断所有的值是否都为正数,因为一个人的年龄不可能为负数吧。
personAge.every(function(currentValue, index){
if (currentValue < 0) {
console.log("Index : " + index + ", value : " + currentValue);
return false;
}
return true;
});
与 every()
方法对应的还有一个方法叫做 some()
,这二者的用法是一样的。这二者的差别在于,every()
方法用于检测一个数组中的元素是否全都满足同一条件,一旦有一个元素不满足条件,方法就会执行结束。而 some()
方法用于检测一个数组中是否有一个元素满足指定条件,如果有一个元素满足,也即回调函数返回了 true
, 那么some()
方法就会结束执行。举个例子,高中的时候每次考完试,总有老师会问,有没有同学考到 90 分啊,当然老师的目的并不是想准确的知道班上有多少人考到 90 分,他只是想知道是否有人突破这一分数线,所以他想得到的答案为 是或否。我们可以用 some()
来模拟这一场景。
var studentGrade = [55,66,77,88,99];
studentGrade.some(function(currentValue, index) {
if (currentValue >= 90) {
console.log("I got " + currentValue + " points!");
return true;
}
return false;
})
一般情况下,我们会利用 for
循环打印整个数组的值,但是为了方便我们也可以使用 forEach()
方法。这个方法的使用和上述介绍的 some(), every()
相同。比如下面利用 forEach()
遍历整个数组。
var numbers = [55,66,77,88,99];
numbers.forEach(function(currentVaule, index) {
console.log("Index : " + index + ", value : " + currentVaule);
});
下面介绍两种能够返回新数组的迭代器方法。filter(callback, thisArg)
可以根据条件返回一个新的数组。
var numbers = [55,66,77,88,99];
var evenNumbers = numbers.filter(function(currentValue, index) {
if (currentValue % 2 == 0) {
return true;
}
return false;
});
for (var i = 0; i < evenNumbers.length; i++) {
console.log(evenNumbers[i]);
}
上面打印的结果为 66,88
。 也就是说,filter()
方法会将满足条件的值返回给新的数组。如果你想求一个数组中所有元素的和,那么 reduce(callback, thisArg)
方法能够帮到你,不过值得注意的是,这个方法的 callback
方法相比于上述介绍的有些差距,它的callback
形如 function(previousValue, currentValue, index, arrayObj)
。下面利用这个函数来求一个数组的和。
var numbers = [55,66,77,88,99];
var result = numbers.reduce(function(previousValue, currentValue, index) {
return previousValue + currentValue;
});
console.log(result);
- 排序
可以利用 reverse()
方法将一个数组中的元素逆序排列。值得注意的是这里的逆序排列只是将数组中的元素按照初始时的反过来的排放而已,并没有涉及到排序问题。
var numbers = [1, 2, 3, 6, 5, 4, 11, 12, 13, 16, 15];
numbers.reverse();
for (var i = 0; i < numbers.length; i++) {
console.log(numbers[i]);
}
这段代码的返回结果为 15,16,13,12,11,4,5,6,3,2,1
。
上面没有涉及到排序,当然我们可以利用 sort()
方法对数组进行排序。
var numbers = [1, 2, 3, 6, 5, 4, 11, 12, 13, 16, 15];
numbers.sort();
for (var i = 0; i < numbers.length; i++) {
console.log(numbers[i]);
}
这段代码的返回结果为 1,11,12,13,15,16,2,3,4,5,6
。 确定这是排序结果?天了噜,这简直是在逗我。为什么会这样呢? 因为 sort()
函数是将数组中的元素当做字符串来排序的,字符串又是根据字符在 ASCII
码来进行排序的。所以,我们只能自己去定义排序规则。
var numbers = [1, 2, 3, 6, 5, 4, 11, 12, 13, 16, 15];
numbers.sort(function(first, second){
return first - second;
});
for (var i = 0; i < numbers.length; i++) {
console.log(numbers[i]);
}
你可以根据自己的需要,为 sort()
指定用于比较的函数,此函数有三种类型的返回值,即 正数,0, 负数。
- 输出为字符串
为了满足某种需要,我们可能会将一个数组作为一个字符串返回,最简单的办法是借助于 toString()
。
var numbers = [66,77,88,99];
var numberString = numbers.toString();
console.log(numberString);
执行结果为 66,77,88,99
。 可见,toString()
方法默认将两个数组元素用逗号隔开,当然我们还可以使用 join()
改变这一默认表现。
var numbers = [66,77,88,99];
var numberString = numbers.join("->");
console.log(numberString);
这时候的输出结果为 66->77->88->99
。
ES6 新增
Array.from()
该方法可以将一些特定的对象转换成数组对象,比如该对象具有 Iterator
接口或者该对象拥有 length
属性。在 ES5
中,如果我们想把 arguments
转换成一个数组,一般会使用以下语句。
[].slice.call(arguments);
Array.prototype.slice.call(arguments);
利用 Array.from()
可以简化上述过程。
Array.from(arguments);
这个函数一共有三个参数,第二个参数的作用类似于 map
, 即对每个位置的元素进行处理,然后写入数组。第三个参数用于绑定 this
。
Array.of()
用于弥补 Array
构造函数的不足,因为对于 new Array()
语句而言,如果只指定一个参数的话,那一定是数组的长度。比如下面这条语句用于创建一个长度为 1 的数组。
var array = new Array(1);
而与此不同的是,Array.of()
无论传入多少参数,都会构建一个数组,并把参数当作元素传入数组,比如下面这条语句就是创建一个长度为 1 而且有一个值为 1 的元素的数组。
var array = Array.of(1);
在 ES5
中,可以用以下语句模拟 Array.of()
的功能。
function ArrayOf() {
return [].slice.call(arguments);
}
copyWithin()
使用这个方法可以将数组内部指定位置的值复制到其他位置,这个变动发生在数组内部。此函数接受三个参数,第一个参数是必须指定的,用于设置从哪个位置开始替换数据。第二个参数是可选的,用于设置从哪个位置开始读取数据,默认为 0,如果为负数,则实际值为当前值加上数组长度。第三个参数是可选的,用于设置在哪个位置终止读取数据,默认值为数组长度的值,如果是负数,则实际值为当前值加上数组长度。下面来看个例子。
var array = [1, 2, 3, 4, 5];
array.copyWithin(0, 3, 5);
console.log(array);
以上操作的意思就是将位置处在 [3, 5) 的元素放在位置 0 及其以后的位置上。所以上面代码的操作结果就是 [4, 5, 3, 4, 5]。
find() 和 findIndex()
从方法的名字中都可以猜出这两个函数的用途,find()
用于查找数组中第一个满足条件的元素值,这个函数像是 some()
和 filter()
的结合。findIndex()
用于返回数组中第一个满足条件的元素的下标。
var array = [1, 2, 3, 4, 5];
// result = 4
var result = array.find(function(value, index) {
return value >= 4;
});
findIndex()
和 find()
用法一样,不过是返回的下标。值得一提的是 find()
和 findIndex()
都能识别 NaN
, 而对于 indexOf()
和 lastIndexOf()
则无法识别 NaN
。
fill()
这个方法一般用与初始化空数组,它可以将数组中的所有元素置为相同的一个值,比如声明一个长度为 5 的数组,并将所有的值都置为 0 。
new Array(5).fill(0);
entries(), keys(), values()
这三个方法用于遍历数组,entries()
用于对键值对进行遍历,keys()
用于对键进行遍历, values()
用于对值进行遍历,下面是一个例子。
var array = ['a', 'b', 'c', 'd', 'e'];
for(let index of array.keys()) {
console.log(index); // 0 1 2 3 4 5
}
for (let value of array.values()) {
console.log(value); // a b c d e
}
for(let item of array.entries()) {
console.log(item); // [0, 'a'] [1, 'b'] ...
}
不过遗憾的是,在我的电脑上没有一个浏览器支持 values()
方法,而 keys()
和 entries()
都是没有问题的。