array 数据类型
array 详细讲解
数组
定义
数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。
上面代码中的a
、b
、c
就构成一个数组,两端的方括号是数组的标志。a
是0号位置,b
是1号位置,c
是2号位置。
除了在定义时赋值,数组也可以先定义后赋值。
任何类型的数据,都可以放入数组。
上面数组arr
的3个成员依次是对象、数组、函数。
如果数组的元素还是数组,就形成了多维数组。
数组的本质
本质上,数组属于一种特殊的对象。typeof
运算符会返回数组的类型是object
。
上面代码表明,typeof
运算符认为数组的类型就是对象。
数组的特殊性体现在,它的键名是按次序排列的一组整数(0,1,2...)。
上面代码中,Object.keys
方法返回数组的所有键名。可以看到数组的键名就是整数0、1、2。
由于数组成员的键名是固定的(默认总是0、1、2...),因此数组不用为每个元素指定键名,而对象的每个成员都必须指定键名。JavaScript 语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。
上面代码分别用数值和字符串作为键名,结果都能读取数组。原因是数值键名被自动转为了字符串。
注意,这点在赋值时也成立。一个值总是先转成字符串,再作为键名进行赋值。
上面代码中,由于1.00
转成字符串是1
,所以通过数字键1
可以读取值。
上一章说过,对象有两种读取成员的方法:点结构(object.key
)和方括号结构(object[key]
)。但是,对于数值的键名,不能使用点结构。
上面代码中,arr.0
的写法不合法,因为单独的数值不能作为标识符(identifier)。所以,数组成员只能用方括号arr[0]
表示(方括号是运算符,可以接受数值)。
length 属性
数组的length
属性,返回数组的成员数量。
JavaScript 使用一个32位整数,保存数组的元素个数。这意味着,数组成员最多只有 4294967295 个(232 - 1)个,也就是说length
属性的最大值就是 4294967295。
只要是数组,就一定有length
属性。该属性是一个动态的值,等于键名中的最大整数加上1
。
上面代码表示,数组的数字键不需要连续,length
属性的值总是比最大的那个整数键大1
。另外,这也表明数组是一种动态的数据结构,可以随时增减数组的成员。
length
属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员数量会自动减少到length
设置的值。
上面代码表示,当数组的length
属性设为2(即最大的整数键只能是1)那么整数键2(值为c
)就已经不在数组中了,被自动删除了。
清空数组的一个有效方法,就是将length
属性设为0。
如果人为设置length
大于当前元素个数,则数组的成员数量会增加到这个值,新增的位置都是空位。
上面代码表示,当length
属性设为大于数组个数时,读取新增的位置都会返回undefined
。
如果人为设置length
为不合法的值,JavaScript 会报错。
值得注意的是,由于数组本质上是一种对象,所以可以为数组添加属性,但是这不影响length
属性的值。
上面代码将数组的键分别设为字符串和小数,结果都不影响length
属性。因为,length
属性的值就是等于最大的数字键加1,而这个数组没有整数键,所以length
属性保持为0
。
如果数组的键名是添加超出范围的数值,该键名会自动转为字符串。
上面代码中,我们为数组arr
添加了两个不合法的数字键,结果length
属性没有发生变化。这些数字键都变成了字符串键名。最后两行之所以会取到值,是因为取键值时,数字键名会默认转为字符串。
in 运算符
检查某个键名是否存在的运算符in
,适用于对象,也适用于数组。
上面代码表明,数组存在键名为2
的键。由于键名都是字符串,所以数值2
会自动转成字符串。
注意,如果数组的某个位置是空位,in
运算符返回false
。
上面代码中,数组arr
只有一个成员arr[100]
,其他位置的键名都会返回false
。
for...in 循环和数组的遍历
for...in
循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。
但是,for...in
不仅会遍历数组所有的数字键,还会遍历非数字键。
上面代码在遍历数组时,也遍历到了非整数键foo
。所以,不推荐使用for...in
遍历数组。
数组的遍历可以考虑使用for
循环或while
循环。
上面代码是三种遍历数组的写法。最后一种写法是逆向遍历,即从最后一个元素向第一个元素遍历。
数组的forEach
方法,也可以用来遍历数组,详见《标准库》的 Array 对象一章。
数组的空位
当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole)。
上面代码表明,数组的空位不影响length
属性。虽然这个位置没有值,引擎依然认为这个位置是有效的。
需要注意的是,如果最后一个元素后面有逗号,并不会产生空位。也就是说,有没有这个逗号,结果都是一样的。
上面代码中,数组最后一个成员后面有一个逗号,这不影响length
属性的值,与没有这个逗号时效果一样。
数组的空位是可以读取的,返回undefined
。
使用delete
命令删除一个数组成员,会形成空位,并且不会影响length
属性。
上面代码用delete
命令删除了数组的第二个元素,这个位置就形成了空位,但是对length
属性没有影响。也就是说,length
属性不过滤空位。所以,使用length
属性进行数组遍历,一定要非常小心。
数组的某个位置是空位,与某个位置是undefined
,是不一样的。如果是空位,使用数组的forEach
方法、for...in
结构、以及Object.keys
方法进行遍历,空位都会被跳过。
如果某个位置是undefined
,遍历的时候就不会被跳过。
这就是说,空位就是数组没有这个元素,所以不会被遍历到,而undefined
则表示数组有这个元素,值是undefined
,所以遍历不会跳过。
类似数组的对象
如果一个对象的所有键名都是正整数或零,并且有length
属性,那么这个对象就很像数组,语法上称为“类似数组的对象”(array-like object)。
上面代码中,对象obj
就是一个类似数组的对象。但是,“类似数组的对象”并不是数组,因为它们不具备数组特有的方法。对象obj
没有数组的push
方法,使用该方法就会报错。
“类似数组的对象”的根本特征,就是具有length
属性。只要有length
属性,就可以认为这个对象类似于数组。但是有一个问题,这种length
属性不是动态值,不会随着成员的变化而变化。
上面代码为对象obj
添加了一个数字键,但是length
属性没变。这就说明了obj
不是数组。
典型的“类似数组的对象”是函数的arguments
对象,以及大多数 DOM 元素集,还有字符串。
上面代码包含三个例子,它们都不是数组(instanceof
运算符返回false
),但是看上去都非常像数组。
数组的slice
方法可以将“类似数组的对象”变成真正的数组。
除了转为真正的数组,“类似数组的对象”还有一个办法可以使用数组的方法,就是通过call()
把数组的方法放到对象上面。
上面代码中,arrayLike
代表一个类似数组的对象,本来是不可以使用数组的forEach()
方法的,但是通过call()
,可以把forEach()
嫁接到arrayLike
上面调用。
下面的例子就是通过这种方法,在arguments
对象上面调用forEach
方法。
字符串也是类似数组的对象,所以也可以用Array.prototype.forEach.call
遍历。
注意,这种方法比直接使用数组原生的forEach
要慢,所以最好还是先将“类似数组的对象”转为真正的数组,然后再直接调用数组的forEach
方法。