zoukankan      html  css  js  c++  java
  • [译]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中的稀疏数组与密集数组
  • 相关阅读:
    Java 猜字谜游戏
    Java中private、protected、public和default的区别
    Java JRT
    Java 数组
    javascript中的bind()方法
    2018-07-31 javascript中对的apply,call
    2018年7月31日
    【转载】javascript回调函数
    函数return以及lodash中的extend,each,map方法
    css实现动态相对居中
  • 原文地址:https://www.cnblogs.com/ziyunfei/p/2802393.html
Copyright © 2011-2022 走看看