在 JavaScript 的第一个版本中,没有数组。它们后来被作为“所有对象之母”的子类引入:Object
。你可以通过这样做很容易地测试它:
var foo = [1,2,3,4];
for (var n in foo)
{//check if n is equal (value and type) to itself, coerced to a number
console.log(n === +(n) ? 'Number' : 'String');
}
这将String
一次又一次地记录。在内部,所有数字键都转换为字符串。Length 属性仅获取最高索引,并将其加 1。而已。当你显示你的数组时,对象被迭代,并且对于每个键,相同的规则适用于任何对象:首先扫描实例,然后是原型......所以如果我们稍微改变我们的代码:
var foo = [1,2,3,4];
foo[9] = 5;
for (var n in foo)
{
if (foo.hasOwnProperty(n))
{//check if current key is an array property
console.log(n === +(n) ? 'Number' : 'String');
}
}
你会注意到数组只有 5 个自己的属性,undefined
键 4-8 是未定义的,因为在实例中没有找到对应的值,在任何底层原型中也没有。简而言之:数组并不是真正的数组,而是行为相似的对象。
正如 Tim 所说,您可以拥有一个具有未定义属性的数组实例,该属性确实存在于该对象中:
var foo = [1,2,undefined,3];
console.log(foo[2] === undefined);//true
console.log(foo[99] === undefined);//true
但同样,有一个区别:
console.log((foo.hasOwnProperty('2') && foo[2] === undefined));//true
console.log((foo.hasOwnProperty('99') && foo[99] === undefined));//false
回顾一下,你的三个主要问题:
更新:
只需引用 Ecma std:
15.4 数组对象
数组对象对某一类属性名称给予特殊处理。当且仅当 ToString(ToUint32(P)) 等于 P 并且 ToUint32(P) 不等于 2^32 1 时,属性名称 P(以字符串值的形式)是数组索引。属性名称为数组索引的属性也称为元素。每个 Array 对象都有一个长度属性,其值始终是小于 2^32 的非负整数。length 属性的值在数值上大于名称为数组索引的每个属性的名称;每当创建或更改 Array 对象的属性时,都会根据需要调整其他属性以保持此不变性。具体来说,每当添加名称为数组索引的属性时,都会更改长度属性,如有必要,比该数组索引的数值大一;并且每当更改长度属性时,所有名称为数组索引且值不小于新长度的属性都会被自动删除。此约束仅适用于 Array 对象的自身属性,不受可能从其原型继承的长度或数组索引属性的影响。
如果以下算法返回 true,则称对象 O 是稀疏的:
1. 设 len 为使用参数“length”调用 O 的 [[Get]] 内部方法的结果。
2. 对于0≤i
a 范围内的每个整数i。令 elem 为使用参数 ToString(i) 调用 O 的 [[GetOwnProperty]] 内部方法的结果。
湾。如果 elem 未定义,则返回 true。
3. 返回假。