[译]JavaScript中的数组

原文:http://www.2ality.com/2012/12/arrays.html

本文要解释一下Javascript中的数组是如何工作的,你将会知道,它们比你想的更像普通对象. 

1.概述

在Javascript中,对象是一个从字符串到值的映射.数组也是对象,只是包含有一些特殊的属性:

  1. 数组索引(下标):如果一个数组对象的属性的数字值(实际上是字符串值)是一个小于232-1的非负整数,则该属性就会被看成是一个数组索引.

  2. "length"属性:该属性的值是一个非负整数,表示了数组的长度.这个长度的值通常是数组的最大索引转换成数字后,再加1.

下面要说的这个表现有时候会让人感到震惊,尤其对于那些刚刚从其他语言转来的人,就是:JavaScript中的数组索引实际上是字符串.(在引擎内部,为了获得更快的访问速度,数组索引通常是用数字来实现的.但是规范就是这么规定的,程序员们看到的表现也是这样的).例如:

> var arr = ['a', 'b', 'c'];
> arr['0']
'a'
> arr[0]
'a'
因为0不是一个合法的标识符(identifier),所以点符号(arr.0)会产生一个语法错误.因此,你必须使用方括号.方括号运算符会将它的操作数转换成字符串,这就是上面的arr[0]为什么能正常工作的原因.数组索引被限制在32比特的范围内.类似于方括号运算符,in运算符也会将它的第一个操作数转换成字符串.这就是为什么能用数字来检查给定索引的数组元素是否存在:
> 2 in [ 'a', 'b', 'c' ]
true
> 3 in [ 'a', 'b', 'c' ]
false
文章下面的几小节将更加深入的讲解数组是如何工作的.

2.稀疏数组

正如我们看到的,数组也是从字符串到值的映射.这就意味着数组可以有孔(hole),一个有孔的数组称之为稀疏数组(sparse array).稀疏数组中索引的个数小于length属性的值.在使用数组字面量定义数组时,你可以通过在逗号前面不写任何值来创建一个孔(最尾部的逗号会被忽略).数组的遍历方法比如forEach和map会忽略掉数组中的孔.下面,让我们比较一下稀疏数组和密集数组(dense array):
> var sparse = [ , , 'c' ];
> var dense  = [ undefined, undefined, 'c' ];

> 0 in sparse
false
> 0 in dense
true

> for(var i=0; i<sparse.length; i++) console.log(sparse[i]);
undefined
undefined
c
> for(var i=0; i<dense.length; i++) console.log(dense[i]);
undefined
undefined
c

> sparse.forEach(function (x) { console.log(x) });
c
> dense.forEach(function (x) { console.log(x) });
undefined
undefined
c

> sparse.map(function (x,i) { return i });
[ , , 2 ]
> dense.map(function (x,i) { return i });
[ 0, 1, 2 ]

> sparse.filter(function () { return true })
[ 'c' ]
> dense.filter(function () { return true })
[ undefined, undefined, 'c' ]

3.数组索引

关于什么样的属性才能称之为数组索引,ECMAScript规范有这样的定义.一个字符串s必须满足下面两个要求,才能成为数组的索引:
 
   要求1: 字符串s 解析成为一个无符号32位整数之后,再转换成字符串的值必须要和s相同.
   要求2: 字符串s 解析成为整数之后的值必须小于232−1 (数组的最大长度).
 
因此,如果按照数值大小比较,一个数组的索引s必须满足下面的范围表达式:
0 ≤ s < 232−1
ToUint32是一个规范内部的方法,它可以将其他的值转换成无符号32位整数.你也可以使用JavaScript代码来实现这个内部方法 [1]:
function ToUint32(x) {
    return x >>> 0;
}
上面的要求1表明了:虽然很多字符串都可以被转换成一个无符号32位整数,但只有其中的少数可以作为合法的数组索引.比如:
> ToUint32('0')
0
> ToUint32('00')
0
> ToUint32('03')
3
> ToUint32('abc')
0
> ToUint32(Math.pow(2,32)+3)
3
上例中只有第一条语句中参数"0"满足了要求1,是个有效的数组索引.

所有不满足数组索引要求的字符串都会被看成普通属性:

> var arr = ['a', 'b', 'c'];
> arr['0']
'a'
> arr['00']
undefined

4.length

译者注:很巧,上周我刚刚写过一篇文章:JavaScript:数组的length属性
数组length属性的值的范围是:
0 ≤ l ≤ 232−1 (32位)

4.1 索引属性的影响

在有新的元素添加时,数组的length属性会自动增大:
> var arr = [];
> arr.length
0
> arr[0] = 'a';
> arr.length
1

4.2 减小length属性

如果length属性当前的值为l,被赋一个新的值l',且l'比原值l小,那么在下面范围内的索引都会被删除.
l'i < l
例如:
> var arr = [ 'a', 'b', 'c' ];
> arr.length
3
> 2 in arr
true
> arr.length = 2;
2
> 2 in arr
false

4.3 增大length属性

增大length属性的值会创建一个稀疏数组:
> var arr = ['a'];
> arr.length = 3;
> arr
[ 'a', , ,]

4.4 length属性的有效值

你可以给length属性赋任何值,引擎内部会使用ToUint32方法将所赋的值转换成数字,转换成的数字必须满足length属性值的合法范围.例如:
> ToUint32('0')  //*
0
> ToUint32('000')  //*
0
> ToUint32('-1')
4294967295
> ToUint32(Math.pow(2,32)-1)  //*
4294967295
> ToUint32(Math.pow(2,32))
0
> ToUint32('abc')
0
上面所有带星号的length赋值是有效的,其他的都是无效的:
> Number('0')
0
> Number('000')
0
> Number('-1')
-1
> Number(Math.pow(2,32)-1)
4294967295
> Number(Math.pow(2,32))
4294967296
> Number('abc')
NaN
你可以测试一下:
> [].length = -1
RangeError: Invalid array length
> [].length = Math.pow(2,32)
RangeError: Invalid array length
> [].length = 'abc'
RangeError: Invalid array length

5.数组实例

数组的对象实例和普通对象非常类似,只是在定义下面两种属性时会有一些额外的操作:
  • 数组索引:可能会增大length属性的值.
  • "length"属性:过大的话会抛出异常,如果新值小于旧值的话会删除超出的元素.
所有其他属性的处理都和普通对象完全相同.

6.超出限制

如果你使用了一个不在索引范围内的索引的话,会发生什么?答案就是该索引会被看成一个普通属性.比如我们设置一个过大的索引值.
> var arr = ['a', 'b'];
> arr[Math.pow(2,32)-1] = 'c';
> arr
[ 'a', 'b' ]
> arr.length
2
> arr[Math.pow(2,32)-1]
'c'
如果你设置一个超大的length属性,也会抛出异常:
> var arr = new Array(Math.pow(2,32)-1)  // max length
> arr.push('x')
RangeError: Invalid array length

译者注:这个地方有个让人吃惊的表现,我刚好刚讲过:JavaScript:数组能越界?

7.建议

使用数组时的两个建议:
  • 假装数组索引就是数字.这正是引擎内部的实现方式,而且这也是ECMAScript未来的大方向.
  • 在对待数组时不要太过聪明.只需要遵循标准的处理模式,引擎通常会知道你想干什么,从而进行对应的优化.不需要你特殊处理.文章“Performance Tips for JavaScript in V8” (作者是Chris Wilson)就讲了几个数组操作相关的建议.

8.相关文章

  1. Integers and shift operators in JavaScript
  2. [译]JavaScript中的稀疏数组与密集数组
posted @ 2012-12-05 16:27  紫云飞  阅读(2368)  评论(4编辑  收藏  举报