文章目录
  1. 1. 数组
    1. 1.1. 数组的定义
    2. 1.2. 数组的本质
    3. 1.3. length属性
    4. 1.4. 类似数组的对象
    5. 1.5. in运算符
    6. 1.6. for…in循环和数组的遍历
    7. 1.7. 数组的空位
  2. 2. 数组对象Array
    1. 2.1. Array对象的静态方法
      1. 2.1.1. isArray方法
    2. 2.2. Array实例的方法
      1. 2.2.1. valueOf方法,toString方法
      2. 2.2.2. push(),pop()
      3. 2.2.3. join(),concat()
      4. 2.2.4. shift(),unshift()
      5. 2.2.5. reverse()
      6. 2.2.6. slice()
      7. 2.2.7. splice()
      8. 2.2.8. sort()
      9. 2.2.9. ECMAScript 5 新加入的数组方法

数组

数组的定义

数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。

1
var arr = ['a', 'b', 'c'];

上面代码中的abc就构成一个数组,两端的方括号是数组的标志。[] a是0号位置,b是1号位置,c是2号位置。

除了在定义时赋值,数组也可以先定义后赋值。

1
2
3
4
5
var arr = [];

arr[0] = 'a';
arr[1] = 'b';
arr[2] = 'c';

任何类型的数据,都可以放入数组。

1
2
3
4
5
6
7
8
9
var arr = [
{a: 1},
[1, 2, 3],
function() {return true;}
];

arr[0] // Object {a: 1}
arr[1] // [1, 2, 3]
arr[2] // function (){return true;}

上面数组arr的3个成员依次是对象、数组、函数。
如果数组的元素还是数组,就形成了多维数组。

1
2
3
var a = [[1, 2], [3, 4]];
a[0][1] // 2
a[1][1] // 4

数组的本质

本质上,数组属于一种特殊的对象。typeof运算符会返回数组的类型是object

1
typeof [1, 2, 3] // "object"

上面代码表明,typeof运算符认为数组的类型就是对象。

数组的特殊性体现在,它的键名是按次序排列的一组整数(0,1,2…)。

1
2
3
4
var arr = ['a', 'b', 'c'];

Object.keys(arr)
// ["0", "1", "2"]

上面代码中,Object.keys方法返回数组的所有键名。可以看到数组的键名就是整数0、1、2。

由于数组成员的键名是固定的,因此数组不用为每个元素指定键名,而对象的每个成员都必须指定键名。

JavaScript语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。

1
2
3
4
var arr = ['a', 'b', 'c'];

arr['0'] // 'a'
arr[0] // 'a'

上面代码分别用数值和字符串作为键名,结果都能读取数组。原因是数值键名被自动转为了字符串。

需要注意的是,这一条在赋值时也成立。如果一个值可以被转换为整数,则以该值为键名,等于以对应的整数为键名。

1
2
3
4
5
6
7
var a = [];

a['1000'] = 'abc';
a[1000] // 'abc'

a[1.00] = 6;
a[1] // 6

上一节说过,对象有两种读取成员的方法:“点”结构(object.key)和方括号结构(object[key])。但是,对于数值的键名,不能使用点结构。

1
2
var arr = [1, 2, 3];
arr.0 // SyntaxError

上面代码中,arr.0的写法不合法,因为单独的数值不能作为标识符(identifier)。所以,数组成员只能用方括号arr[0]表示(方括号是运算符,可以接受数值)。

length属性

数组的length属性,返回数组的成员数量。

1
['a', 'b', 'c'].length // 3

JavaScript使用一个32位整数,保存数组的元素个数。这意味着,数组成员最多只有4294967295个(232 - 1)个,也就是说length属性的最大值就是4294967295。
数组的length属性与对象的length属性有区别,只要是数组,就一定有length属性,而对象不一定有。而且,数组的length属性是一个动态的值,等于键名中的最大整数加上1

1
2
3
4
5
6
7
8
9
10
11
var arr = ['a', 'b'];
arr.length // 2

arr[2] = 'c';
arr.length // 3

arr[9] = 'd';
arr.length // 10

arr[1000] = 'e';
arr.length // 1001

上面代码表示,数组的数字键不需要连续,length属性的值总是比最大的那个整数键大1。另外,这也表明数组是一种动态的数据结构,可以随时增减数组的成员。
length属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员会自动减少到length设置的值。

1
2
3
4
5
var arr = [ 'a', 'b', 'c' ];
arr.length // 3

arr.length = 2;
arr // ["a", "b"]

上面代码表示,当数组的length属性设为2(即最大的整数键只能是1)那么整数键2(值为c)就已经不在数组中了,被自动删除了。

将数组清空的一个有效方法,就是将length属性设为0

1
2
3
4
var arr = [ 'a', 'b', 'c' ];

arr.length = 0;
arr // []

如果人为设置length大于当前元素个数,则数组的成员数量会增加到这个值,新增的位置都是空位。

1
2
3
4
var a = ['a'];

a.length = 3;
a[1] // undefined

上面代码表示,当length属性设为大于数组个数时,读取新增的位置都会返回undefined

如果人为设置length为不合法的值,JavaScript会报错。

1
2
3
4
5
6
7
8
9
10
11
// 设置负值
[].length = -1
// RangeError: Invalid array length

// 数组元素个数大于等于2的32次方
[].length = Math.pow(2,32)
// RangeError: Invalid array length

// 设置字符串
[].length = 'abc'
// RangeError: Invalid array length

值得注意的是,由于数组本质上是对象的一种,所以我们可以为数组添加属性,但是这不影响length属性的值。

1
2
3
4
5
6
7
var a = [];

a['p'] = 'abc';
a.length // 0

a[2.1] = 'abc';
a.length // 0

上面代码将数组的键分别设为字符串和小数,结果都不影响length属性。因为,length属性的值就是等于最大的数字键加1,而这个数组没有整数键,所以length属性保持为0

类似数组的对象

在JavaScript中,有些对象被称为“类似数组的对象”(array-like object)。意思是,它们看上去很像数组,可以使用length属性,但是它们并不是数组,所以无法使用一些数组的方法。

下面就是一个类似数组的对象。

1
2
3
4
5
6
7
8
9
10
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};

obj[0] // 'a'
obj[2] // 'c'
obj.length // 3

上面代码的变量obj是一个对象,但是看上去跟数组很像。所以只要有数字键和length属性,就是一个类似数组的对象。当然,变量obj无法使用数组特有的一些方法,比如poppush方法。而且,length属性不是动态值,不会随着成员的变化而变化。

1
2
3
4
5
var obj = {
length: 0
};
obj[3] = 'd';
obj.length // 0

上面代码为对象obj添加了一个数字键,但是length属性没变。这就说明了obj不是数组。

典型的类似数组的对象是函数的arguments对象,以及大多数DOM元素集,还有字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// arguments对象
function args() { return arguments }
var arrayLike = args('a', 'b');

arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false

// DOM元素集
var elts = document.getElementsByTagName('h3');
elts.length // 3
elts instanceof Array // false

// 字符串
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array // false

数组的slice方法将类似数组的对象,变成真正的数组。

1
var arr = Array.prototype.slice.call(arrayLike);

遍历类似数组的对象,可以采用for循环,也可以采用数组的forEach方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
// for循环
function logArgs() {
for (var i = 0; i < arguments.length; i++) {
console.log(i + '. ' + arguments[i]);
}
}

// forEach方法
function logArgs() {
Array.prototype.forEach.call(arguments, function (elem, i) {
console.log(i+'. '+elem);
});
}

由于字符串也是类似数组的对象,所以也可以用Array.prototype.forEach.call遍历。

1
2
3
4
5
6
Array.prototype.forEach.call('abc', function(chr) {
console.log(chr);
});
// a
// b
// c

in运算符

检查某个键名是否存在的运算符in,适用于对象,也适用于数组。

1
2
2 in [ 'a', 'b', 'c' ] // true
'2' in [ 'a', 'b', 'c' ] // true

上面代码表明,数组存在键名为2的键。由于键名都是字符串,所以数值2会自动转成字符串。

for…in循环和数组的遍历

for...in循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。

1
2
3
4
5
6
7
8
var a = [1, 2, 3];

for (var i in a) {
console.log(a[i]);
}

// 1
// 2
// 3

但是,for...in不仅会遍历数组所有的数字键,还会遍历非数字键。

1
2
3
4
5
6
7
8
9
10
var a = [1, 2, 3];
a.foo = true;

for (var key in a) {
console.log(key);
}
// 0
// 1
// 2
// foo

上面代码在遍历数组时,也遍历到了非整数键foo。所以,不推荐使用for...in遍历数组。

数组的遍历可以考虑使用for循环或while循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a = [1, 2, 3];

// for循环
for(var i = 0; i < a.length; i++) {
console.log(a[i]);
}


// while循环
var i = 0;
while (i < a.length) {
console.log(a[i]);
i++;
}


var l = a.length;
while (l--) {
console.log(a[l]);
}

上面代码是三种遍历数组的写法。最后一种写法是逆向遍历,即从最后一个元素向第一个元素遍历。

数组的forEach方法,也可以用来遍历数组,详见《标准库》一章的Array对象部分。

1
2
3
4
var colors = ['red', 'green', 'blue'];
colors.forEach(function (color) {
console.log(color);
});

数组的空位

当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole)。

1
2
var a = [1, , 1];
a.length // 3

上面代码表明,数组的空位不影响length属性。

需要注意的是,如果最后一个元素后面有逗号,并不会产生空位。也就是说,有没有这个逗号,结果都是一样的。

1
2
3
4
var a = [1, 2, 3,];

a.length // 3
a // [1, 2, 3]

上面代码中,数组最后一个成员后面有一个逗号,这不影响length属性的值,与没有这个逗号时效果一样。

数组的空位是可以读取的,返回undefined

1
2
var a = [, , ,];
a[1] // undefined

使用delete命令删除一个值,会形成空位。

1
2
3
4
var a = [1, 2, 3];

delete a[1];
a[1] // undefined

delete命令不影响length属性。

1
2
3
4
var a = [1, 2, 3];
delete a[1];
delete a[2];
a.length // 3

上面代码用delete命令删除了两个键,对length属性没有影响。也就是说,length属性不过滤空位。所以,使用length属性进行数组遍历,一定要非常小心。

数组的某个位置是空位,与某个位置是undefined,是不一样的。如果是空位,使用数组的forEach方法、for...in结构、以及Object.keys方法进行遍历,空位都会被跳过。

数组对象Array

Array是JavaScript的内置对象,同时也是一个构造函数,可以用它生成新的数组。作为构造函数时,Array可以接受参数,但是不同的参数,会使得Array产生不同的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 无参数时,返回一个空数组
new Array() // []

// 单个正整数参数,表示返回的新数组的长度
new Array(1) // [undefined × 1]
new Array(2) // [undefined x 2]

// 单个非正整数参数(比如字符串、布尔值、对象等),
// 则该参数是返回的新数组的成员
new Array('abc') // ['abc']
new Array([1]) // [Array[1]]

// 多参数时,所有参数都是返回的新数组的成员
new Array(1, 2) // [1, 2]

从上面代码可以看到,Array作为构造函数,行为很不一致。因此,不建议使用它生成新数组,直接使用数组的字面量是更好的方法。

1
2
3
4
5
// bad
var arr = new Array(1, 2);

// good
var arr = [1, 2];

另外,Array作为构造函数时,如果参数是一个正整数,返回的空数组虽然可以取到length属性,但是取不到键名。

1
2
3
4
5
6
7
8
9
Array(3).length // 3

Array(3)[0] // undefined
Array(3)[1] // undefined
Array(3)[2] // undefined

0 in Array(3) // false
1 in Array(3) // false
2 in Array(3) // false

上面代码中,Array(3)是一个长度为3的空数组。虽然可以取到每个位置的键值,但是所有的键名都取不到,实际上是产生了3个空位。JavaScript语言的设计规格,就是这么规定的,虽然不是一个大问题,但是还是必须小心。这也是不推荐使用Array构造函数的一个理由。

Array对象的静态方法

isArray方法

Array.isArray方法用来判断一个值是否为数组。它可以弥补typeof运算符的不足。

1
2
3
4
var a = [1, 2, 3];

typeof a // "object"
Array.isArray(a) // true

上面代码表示,typeof运算符只能显示数组的类型是Object,而Array.isArray方法可以对数组返回true

Polyfill

1
2
3
4
5
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}

Array实例的方法

以下这些Array实例对象的方法,都是数组实例才能使用。如果不想创建实例,只是想单纯调用这些方法,可以写成[].method.call(调用对象,参数) 的形式,或者Array.prototype.method.call(调用对象,参数)的形式。

valueOf方法,toString方法

valueOf方法返回数组本身。

1
2
var a = [1, 2, 3];
a.valueOf() // [1, 2, 3]

toString方法返回数组的字符串形式。

1
2
3
4
5
var a = [1, 2, 3];
a.toString() // "1,2,3"

var a = [1, 2, 3, [4, 5, 6]];
a.toString() // "1,2,3,4,5,6"

push(),pop()

push方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组

1
2
3
4
5
6
var a = [];

a.push(1) // 1
a.push('a') // 2
a.push(true, {}) // 4
a // [1, 'a', true, {}]

上面代码使用push方法,先后往数组中添加了四个成员。如果需要合并两个数组,可以这样写。

1
2
3
4
5
6
7
8
9
10
11
var a = [1, 2, 3];
var b = [4, 5, 6];

Array.prototype.push.apply(a, b)
// 或者
a.push.apply(a, b)

// 上面两种写法等同于
a.push(4, 5, 6)

a // [1, 2, 3, 4, 5, 6]

push方法还可以用于向对象添加元素,添加后的对象变成类似数组的对象,即新加入元素的键对应数组的索引,并且对象有一个length属性。

1
2
3
4
5
6
7
var a = {a: 1};

[].push.call(a, 2);
a // {a:1, 0:2, length: 1}

[].push.call(a, [3]);
a // {a:1, 0:2, 1:[3], length: 2}

pop方法用于删除数组的最后一个元素,并返回该元素。注意,该方法会改变原数组

1
2
3
4
var a = ['a', 'b', 'c'];

a.pop() // 'c'
a // ['a', 'b']

对空数组使用pop方法,不会报错,而是返回undefined

1
[].pop() // undefined

pushpop结合使用,就构成了“后进先出”的栈结构(stack)。

join(),concat()

join方法以参数作为分隔符,将所有数组成员组成一个字符串返回。如果不提供参数,默认用逗号分隔。

1
2
3
4
5
var a = [1, 2, 3, 4];

a.join(' ') // '1 2 3 4'
a.join(' | ') // "1 | 2 | 3 | 4"
a.join() // "1,2,3,4"

通过call方法,join方法(即Array.prototype.join)也可以用于字符串。

1
2
Array.prototype.join.call('hello', '-')
// "h-e-l-l-o"

concat方法用于多个数组的合并。它将新数组的成员,添加到原数组的尾部,然后返回一个新数组,原数组不变。

1
2
3
4
5
['hello'].concat(['world'])
// ["hello", "world"]

['hello'].concat(['world'], ['!'])
// ["hello", "world", "!"]

除了接受数组作为参数,concat也可以接受其他类型的值作为参数。它们会作为新的元素,添加数组尾部。

1
2
3
4
[1, 2, 3].concat(4, 5, 6)
// [1, 2, 3, 4, 5, 6]

[1, 2, 3].concat(4, [5, 6])

如果不提供参数,concat方法返回当前数组的一个浅拷贝。所谓“浅拷贝”,指的是如果数组成员包括复合类型的值(比如对象),则新数组拷贝的是该值的引用。

1
2
3
4
5
6
7
var obj = { a:1 };
var oldArray = [obj];

var newArray = oldArray.concat();

obj.a = 2;
newArray[0].a // 2

上面代码中,原数组包含一个对象,concat方法生成的新数组包含这个对象的引用。所以,改变原对象以后,新数组跟着改变。事实上,只要原数组的成员中包含对象,concat方法不管有没有参数,总是返回该对象的引用。
concat方法也可以用于将对象合并为数组,但是必须借助call方法。

1
2
3
4
5
6
7
8
9
10
[].concat.call({ a: 1 }, { b: 2 })
// [{ a: 1 }, { b: 2 }]

[].concat.call({ a: 1 }, [2])
// [{a:1}, 2]

// 等同于

[2].concat({a:1})
Array.prototype.concat.call({ a: 1 }, { b: 2 })

shift(),unshift()

shift方法用于删除数组的第一个元素,并返回该元素。注意,该方法会改变原数组

1
2
3
4
var a = ['a', 'b', 'c'];

a.shift() // 'a'
a // ['b', 'c']

shift方法可以遍历并清空一个数组。

1
2
3
4
5
6
7
8
var list = [1, 2, 3, 4, 5, 6];
var item;

while (item = list.shift()) {
console.log(item);
}

list // []

pushshift结合使用,就构成了“先进先出”的队列结构(queue)。

unshift方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组

1
2
3
4
var a = ['a', 'b', 'c'];

a.unshift('x'); // 4
a // ['x', 'a', 'b', 'c']

reverse()

reverse方法用于颠倒数组中元素的顺序,使用这个方法以后,返回改变后的原数组

1
2
3
4
var a = ['a', 'b', 'c'];

a.reverse() // ["c", "b", "a"]
a // ["c", "b", "a"]

slice()

slice方法用于提取原数组的一部分,返回一个新数组,原数组不变

它的第一个参数为起始位置(从0开始),第二个参数为终止位置(但该位置的元素本身不包括在内,包含头不包含尾)。如果省略第二个参数,则一直返回到原数组的最后一个成员。

1
2
3
4
5
6
7
8
9
10
// 格式
arr.slice(start_index, upto_index);

// 用法
var a = ['a', 'b', 'c'];

a.slice(0) // ["a", "b", "c"]
a.slice(1) // ["b", "c"]
a.slice(1, 2) // ["b"]
a.slice(2, 6) // ["c"]

如果slice方法的参数是负数,则表示倒数计算的字符串位置。

1
2
3
var a = ['a', 'b', 'c'];
a.slice(-2) // ["b", "c"] 相当于 a.slice(a.length + (-2))
a.slice(-2, -1) // ["b"] 相当于 a.slice(a.length + (-2), a.length + (-1))

如果参数值大于数组成员的个数,或者第二个参数小于第一个参数,则返回空数组。

1
2
3
var a = ['a', 'b', 'c'];
a.slice(4) // []
a.slice(2, 1) // []

slice方法的一个重要应用,是将类似数组的对象转为真正的数组。

1
2
3
4
5
Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })
// ['a', 'b']

Array.prototype.slice.call(document.querySelectorAll("div"));
Array.prototype.slice.call(arguments);

上面代码的参数都不是数组,但是通过call方法,在它们上面调用slice方法,就可以把它们转为真正的数组。

splice()

splice方法用于删除原数组的一部分成员,并可以在被删除的位置添加入新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组。
splice的第一个参数是删除的起始位置,第二个参数是被删除的元素个数。如果后面还有更多的参数,则表示这些就是要被插入数组的新元素。

1
2
3
4
5
6
7
// 格式
arr.splice(index, count_to_remove, addElement1, addElement2, ...);

// 用法
var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2) // ["e", "f"]
a // ["a", "b", "c", "d"]

上面代码从原数组位置4开始,删除了两个数组成员。

1
2
3
var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2, 1, 2) // ["e", "f"]
a // ["a", "b", "c", "d", 1, 2]

上面代码除了删除成员,还插入了两个新成员。

如果只是单纯地插入元素,splice方法的第二个参数可以设为0。

1
2
3
4
var a = [1, 1, 1];

a.splice(1, 0, 2) // []
a // [1, 2, 1, 1]

如果只提供第一个参数,则实际上等同于将原数组在指定位置拆分成两个数组。

1
2
3
var a = [1, 2, 3, 4];
a.splice(2) // [3, 4]
a // [1, 2]

sort()

sort方法对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变。

1
2
3
4
5
6
7
8
9
10
11
['d', 'c', 'b', 'a'].sort()
// ['a', 'b', 'c', 'd']

[4, 3, 2, 1].sort()
// [1, 2, 3, 4]

[11, 101].sort()
// [101, 11]

[10111,1101,111].sort()
// [10111, 1101, 111]

上面代码的最后两个例子,需要特殊注意。sort方法不是按照大小排序,而是按照对应字符串的字典顺序排序,所以101排在11的前面。

如果想让sort方法按照自定义方式排序,可以传入一个函数作为参数,表示按照自定义方法进行排序。
该函数本身又接受两个参数,表示进行比较的两个元素。如果返回值大于0,表示第一个元素排在第二个元素后面;其他情况下,都是第一个元素排在第二个元素前面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[10111,1101,111].sort(function (a,b){
return a - b;
})
// [111, 1101, 10111]

[
{ name: "张三", age: 30 },
{ name: "李四", age: 24 },
{ name: "王五", age: 28 }
].sort(function(o1, o2) {
return o1.age - o2.age;
})
// [
// { name: "李四", age: 24 },
// { name: "王五", age: 28 },
// { name: "张三", age: 30 }
// ]

ECMAScript 5 新加入的数组方法

ECMAScript 5新增了9个数组实例的方法,分别是mapforEachfiltereverysomereducereduceRightindexOflastIndexOf。其中,前7个与函数式(functional)操作有关。
这些方法可以在数组上使用,也可以在字符串和类似数组的对象上使用,这是它们不同于传统数组方法的一个地方。
在用法上,这些方法的参数是一个函数,这个作为参数的函数本身又接受三个参数:数组的当前元素elem、该元素的位置index和整个数组arr(详见下面的实例)。另外,上下文对象(context)可以作为第二个参数,传入forEach(), every(), some(), filter(), map()方法,用来绑定函数运行时的上下文。
对于不支持这些方法的老式浏览器(主要是IE 8及以下版本),可以使用函数库es5-shim,或者UnderscoreLo-Dash

转自:阮一峰教程

文章目录
  1. 1. 数组
    1. 1.1. 数组的定义
    2. 1.2. 数组的本质
    3. 1.3. length属性
    4. 1.4. 类似数组的对象
    5. 1.5. in运算符
    6. 1.6. for…in循环和数组的遍历
    7. 1.7. 数组的空位
  2. 2. 数组对象Array
    1. 2.1. Array对象的静态方法
      1. 2.1.1. isArray方法
    2. 2.2. Array实例的方法
      1. 2.2.1. valueOf方法,toString方法
      2. 2.2.2. push(),pop()
      3. 2.2.3. join(),concat()
      4. 2.2.4. shift(),unshift()
      5. 2.2.5. reverse()
      6. 2.2.6. slice()
      7. 2.2.7. splice()
      8. 2.2.8. sort()
      9. 2.2.9. ECMAScript 5 新加入的数组方法