数组Array

前端开发
2018年12月07日
427

与其他语言不同,ECMAScript的数组的每一项都可以保存任何类型的数据,而且,数组的大小是可以动态调整的。

创建数组

创建数组的基本方式有两种:第一种是使用Array构造函数。

js
var colors = new Array(); // or var colors = new Array(20); // or var colors = new Array('red', 'green', 'blue'); // or 可以省略new var colors = Array();

第二种是使用数组字面量表示法

js
var colors = ['red', 'green', 'blue']; var colors = []; var colors = [1, 2,]; var colors = [, , , ,];

读取和设置值

在读取和设置数组的值时,要使用方括号[]并提供相应值的索引:

js
var colors = ['red', 'green', 'blue']; console.log(colors[0]); // 'red' colors[2] = 'black'; // ['red', 'green', 'black'] colors[3] = 'brown'; // ['red', 'green', 'black', 'brown']

长度

数组的长度(项的数量)保存在其length属性中,这个属性始终会返回0或更大的值。有意思的是,length属性它不是只读的,因此,通过设置这个属性,可以从数组的末尾移除或向数组中添加新项:

js
var colors = ['red', 'green', 'blue']; console.log(colors.length); // 3 colors.length = 2; // colors: ['red', 'green'] // 如果将length属性设置为大于数组长度的值,则会新增的每一项都会取得undefined值。 colors.length = 3; console.log(colors[3]); // undefined

利用length这个属性也可以很方便地在数组末尾添加新项:

js
var colors = ['red', 'green', 'blue']; colors[colors.length] = 'brown'; // ['red', 'green', 'blue', 'brown'] colors[colors.length] = 'black'; // ['red', 'green', 'blue', 'brown', 'black']

注意:数组最多可以包含4294967295个项,如果超过这个上限值,就会发生异常。而创建一个初始大小与这个上限值接近的数组,则可能会导致运行时间超长的脚本错误。

检测数组

自从ECMAScript做出规定以后,就出现了确定某个对象是不是数组的经典问题。对于一个网页或者一个全局作用域而言,全用instanceof就能得到满意的结果:

js
if (value instanceof Array) { // ... }

instanceof的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。如果一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。

为了解决这个问题,ES5新增了Array.isArray()方法。这个方法的目的是确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。

js
if (Array.isArray(value)) { // ... }

转换方法

所有对象都具有toLocaleString()toString()valueOf()方法,数组也不例外。其中,调用valueOf()返回的还是数组本身;而调用数组的toString()会返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串,实际上,为了生成这个字符串会调用数组里每一项的toString()方法

js
var colors = ['red', 'green', 'blue']; console.log(colors.toString()); // red,green,blue console.log(colors.valueOf()); // [ 'red', 'green', 'blue' ] console.log(colors); // [ 'red', 'green', 'blue' ]

toLocaleString()经常也会返回与toString()相同的值,但也不总是如此。当调用toLocaleString()时,它调用的是每一项的toLocaleString()方法,看下面这个例子:

js
// 分别创建两person对象,它们都拥有toLocaleString方法和toString()方法 var person1 = { toLocaleString: function () { return 'person1 toLocaleString'; }, toString: function () { return 'person1 toString'; } } var person2 = { toLocaleString: function () { return 'person2 toLocaleString'; }, toString: function () { return 'person2 toString'; } } var people = [person1, person2]; console.log(people); console.log(people.toLocaleString()); // person1 toLocaleString,person2 toLocaleString console.log(people.toString()); // person1 toString,person2 toString

栈方法

数组也提供了一种让数组的行为类似于其他数据结构的方法。具体说来,数组可以表现得就像栈一样,后者是一种可以限制插入和删除项的数据结构。栈是一种LIFO(Last-In-First-Out,后进先出)的数据结构,也就是最新添加的项最早被移除。ECMAScript为数组专门提供了push()pop()方法,以便实现类似栈的行为。

push()可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度

pop()则从数组末尾移除最后一项,并返回被移除的项

js
var colors = new Array(); var count = colors.push('red', 'green'); console.log(count); // 2 count = colors.push('black'); console.log(count); // 3 var item = colors.pop(); console.log(item); // 'black'; console.log(colors.length); // 2

队列方法

队列数据结构的访问规则是FIFO(First-In-First-Out,先进先出)。队列在列表的末端添加项,从列表的前端移除项。而从列表前端移除项则需要使用到数组的shift()方法,它能够移除数组中的第一个项,并返回被移除项。结合使用shift()push()就可以像使用队列一样使用数组。

js
var colors = new Array(); var count = colors.push('red', 'green'); console.log(count); // 2 var item = colors.shift(); console.log(item); // 'red' console.log(colors.length); // 1

ECMAScript还为数组提供了一个unshift()方法它能在数组前端添加任意数量的项并返回新数组的长度。因此,使用unshift()pop()方法,可以从相反方向来模拟队列。

重排序方法

数组有两个可以直接用来重排序的方法:reserve()sort()

reserve()会反转数组项的顺序。

js
var values = [1, 2, 3, 4, 5]; values.reserve(); console.log(values); // [5, 4, 3, 2, 1]

sort()按升序排列数组项。为了实现排序,sort()会调用每个数组项的toString()方法,然后比较得到的字符串,以确定如何排序。即使数组中的每一项都是数值,sort()比较的也是字符串。

js
var values = [0, 1, 5, 10, 15]; values.sort(); console.log(values); // [0, 1, 10, 15, 5];

从上面的例子可以看见,即使例子中值的顺序没有问题,但sort也会根据字符串的比较结果改变原来的顺序。因为数值5虽然小于10,但在进行字符串比较时,'10'则位于'5'的前面,于是数组的顺序就被修改了。

sort()可以接收一个比较函数作为参数。这个比较函数接收两个参数,有三种情况的返回结果:

  • 如果第一个参数应该位于第二个参数之前,则返回一个负数;
  • 如果第一个参数应该位于第二个参数之后,则返回一个正数;
  • 如果两个参数相等,则返回0。
js
// 这是一个简单的比较函数 function compare (val1, val2) { if (val1 < val2) { return -1; } else if (val1 > val2) { return 1; } else { return 0; } } var values = [0, 5, 10, 15, 1]; values.sort(compare); console.log(values); // [ 0, 1, 5, 10, 15 ]

对于数值类型或者其valueOf()会返回数值类型的对象类型,可以使用一个更简单的比较函数:

js
function compare (val1, val2) { return val2 - val1; }

操作方法

合并concat()

ECMAScript为操作已经包含在数组中的项提供了很多方法。其中,concat()方法可以基于当前数组中所有项创建一个新数组。具体来说,这个方法会先创建当前数组的一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。

js
var colors = ['red', 'green', 'blue']; var colors2 = colors.concat('yellow', ['black', 'brown']); console.log(colors2); // ['red', 'green', 'blue', 'yellow', 'black', 'brown']

截取slice()

slice()方法能够基于当前数组中的一个或多个项创建一个新数组,slice()方法接受一个或两个参数,即要返回的项的起始位置和结束位置。

js
var colors = ['red', 'green', 'blue', 'yellow', 'brown']; var colors2 = colors.slice(1); // 只有一个参数的情况,返回指定位置到数组的末尾的所有项。 var colors3 = colors.slice(1, 4); // 两个参数的情况,则返回起始位置1到结束位置4(不包含位置4)的项。 console.log(color2); // ['green', 'blue', 'yellow', 'brown'] console.log(color3); // ['green', 'blue', 'yellow']

删除、插入、替换splice()

splice()方法恐怕要算是最强大的数组方法了,它有很多种用法。主要用途是向数组中部插入项,但使用这种方法的方式则有如下3种:

  • 删除:删除指定数量的项,只需指定2个参数(要删除的第一项的位置和删除的数目);
  • 插入:向指定的位置插入任意数量的项,参数分别为起始位置、要删除的项数(0)和要插入的项。如果要插入多个项,可以再传入第四、第五,以至任意多个项。
  • 替换:向指定的位置插入任意数量的项,参数同上,只不过要指定删除的项数。

splice()方法始终都会返回一个数组,该数组中包含从原始数组中删除的项,如果没有删除项,则返回一个空数组。

js
var colors = ['red', 'green', 'blue']; var removed = colors.splice(0, 1); console.log(colors); // [ 'green', 'blue' ] console.log(removed); // [ 'red' ] removed = colors.splice(1, 0, 'yellow', 'brown'); console.log(colors); // [ 'green', 'yellow', 'brown', 'blue' ] console.log(removed); // [] removed = colors.splice(1, 1, 'red', 'purple'); console.log(colors); // [ 'green', 'red', 'purple', 'brown', 'blue' ] console.log(removed); // [ 'yellow' ]

位置方法

ECMAScript5为数组实例添加了两个位置方法:indexOf()lastIndexOf()。这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中,indexOf()方法从数组开头开始向后查找,lastIndexOf()则从数组末尾开始向前查找。

这两个方法都返回要查找的项在数组中的位置,如果没找到则返回-1。

js
var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1]; console.log(numbers.indexOf(4)); // 3 console.log(numbers.indexOf(numbers.length + 1)); // -1

迭代方法

ES5为数组定义了5个迭代方法。每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响this的值。传入这些方法的函数会接收三个参数:数组项的值、位置和数组本身

  • every():如果该函数对每一项都返回true,则返回true。
  • filter():返回该函数会返回true的项组成的数组。
  • forEach():没有返回值。
  • map():返回每次函数调用的结果组成的数组。
  • some():如果该函数有一项返回true,则返回true。
js
var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1]; var everyResult = numbers.every(function (item, index, array) { return (item > 2); }) console.log(everyResult); // false var filterResult = numbers.filter(function (item, index, array) { return item > 2; }); console.log(filterResult); // [3, 4, 5, 4, 3]; number.forEach(function (item, index, array) { // 对每一项执行某些操作 }); var mapResult = numbers.map(function (item, index, array) { return item * 2; }); console.log(mapResult); // [2, 4, 6, 8, 10, 8, 6, 4, 2] var someResult = numbers.some(function (item, index, array) { return item > 2; }); console.log(someResult); // true

归并方法

ES5还新增了两个归并数组的方法:reduce()reduceRight()。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。两个方法的不同之处是:前者从第一项开始,遍历到最后;后者从最后一项开始,遍历到第一项。

这两个方法都接收两个参数:对每一项调用的函数和(可选的)作为归并基础的初始值。而传入的函数接收4个参数:前一个值、当前值、当前项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上。

js
var values = [1, 2, 3, 4, 5]; var sum = values.reduce(function (prev, curr, index, array) { console.log(index); return prev + curr; }); console.log('sum = %d', sum); // 1 // 2 // 3 // 4 // sum = 15