JavaScript数组对象与类数组对象的区别 - 澄清

6
我尝试将一个声明为数组对象的类似数组的对象字符串化,发现当它被定义为数组对象时,JSON.stringify无法正确处理类似数组的对象。
请参见下面更明确的内容 --> jsFiddle
var simpleArray = []; //note that it is defined as Array Object 

alert(typeof simpleArray); // returns object -> Array Object

simpleArray ['test1'] = 'test 1';
simpleArray ['test2'] = 'test 2';

alert(JSON.stringify(simpleArray)); //returns [] 

它可以正常工作,并在我将 var simpleArray = []; 更改为 var simpleArray = {}; 时返回了 {"test1":"test 1","test2":"test 2"}。有人能否提供一些解释或参考资料,让我可以阅读更多内容? 编辑: 问题:当 typeof simpleArray = []simpleArray = {} 返回对象时,为什么 JSON.stringify 无法在两种情况下都返回 {"test1":"test 1","test2":"test 2"}
7个回答

5
这里的区别在于索引。当您使用数组[]时,索引只能为正整数。
所以下面的写法是错误的:
var array = [ ];
array['test1'] = 'test 1';
array['test2'] = 'test 2';

因为test1test2不是整数。为了修复它,您需要使用基于整数的索引:
var array = [ ];
array[0] = 'test 1';
array[1] = 'test 2';

如果您声明一个JavaScript对象,那么属性可以是任何字符串:

var array = { };
array['test1'] = 'test 1';
array['test2'] = 'test 2';

这与以下代码等价:

var array = { };
array.test1 = 'test 1';
array.test2 = 'test 2';

当您执行 var simpleArray = []; simpleArray ['test2'] = 'test 2'; 时会发生什么? - Matt Fenwick
3
@MattFenwick,发生的情况与你可能期望的相反:simpleArray.length = 0,这意味着该数组中没有元素。您仍然可以使用simpleArray.test2来访问您定义的属性。这只是应该避免的令人困惑的代码。 - Darin Dimitrov
+1 对于详细的解释表示感谢,但我想知道的是为什么当声明为[]时,JSON.stringify无法处理。我尝试使用typeof并在两种情况下都返回对象。我将把这个问题添加到我的问题中。 - Selvakumar Arumugam
1
@SKS,你已经将simpleArray声明为数组 - []。这意味着您需要使用基于整数的索引。在你的例子中,你没有这样做。所以在这种情况下,simpleArray.length = 0,就好像这个数组没有包含任何元素一样。因此,JSON.stringify方法将序列化这个空数组为[]是完全正常的。你刚刚违反了数组必须具有基于整数的索引的规则,当你违反规则时会发生一些不好的事情。 - Darin Dimitrov
1
我能够理解这一点,但是当typeof返回对象并且JSON.stringify无法处理时,我想知道原因。现在我清楚地了解到它使用if(Object.prototype.toString.apply(value)=== '[object Array]')检测为数组,并遍历长度为0,因此结果为[]。再次感谢。 - Selvakumar Arumugam

2
“有人能解释一下或者给我提供一些参考资料吗?”
当你处理 JSON 数据时,可以参考 json.org 阅读规范要求。在 JSON 中,一个数组(Array)是由逗号分隔的值的有序列表。

enter image description here

因此,JSON.stringify() 简单地忽略了任何无法表示为简单有序列表的内容。

如果您执行以下操作...

var simpleArray = [];
simpleArray.foo = 'bar';

“你仍然提供了一个数组,因此它只期望数字索引,并忽略其他任何内容。
由于JSON是语言无关的,处理JSON的方法需要决定哪种语言结构最适合每个JSON结构。
因此,JSON具有以下结构...”
{} // object
[] // array

你应该注意,尽管它们看起来非常类似于JavaScript对象和数组,但它们并不完全相同。
用于创建JSON结构的任何JavaScript结构都必须符合JSON结构允许的要求。这就是为什么非数字属性被排除在外的原因。
虽然JavaScript对它们没有问题,但JSON不会容忍它们。

非数字属性(更好的说法是具有非数字名称的属性)不会被删除。它们首先就不被视为迭代对象。 - PointedEars
@PointedEars:措辞不当。我的意思是从开发者的角度看,从Array对象中删除,就像“嘿,它们在我的Array对象中,但不在我的JSON文本中!”我应该说“排除”。 - user1106925

2

你不需要一个数组。当你在JavaScript中需要一个“关联数组”时,你实际上需要一个对象,{}

你可以使用instanceof Array来区分它们:

[] instanceof Array // true
({}) instanceof Array // false

编辑:它可以处理它。它序列化数组的所有元素。但是,要成为一个元素,必须有一个数字键。在这种情况下,没有。

这与JSON无关。它与toSourcelength属性一致。


我理解了那部分,我想知道的是为什么当声明为[]时,JSON.stringify无法处理。 - Selvakumar Arumugam
谢谢!我正在寻找instanceof。我试图使用typeof,但在两种情况下都返回对象。 - Selvakumar Arumugam
1
自从ES 5以来,也有Array.isArray(...)(15.4.3.2),相比之下它是框架安全的。这可以被模拟。 - PointedEars

1

JSON.stringify 遇到一个数组时,它会以类似于 for 循环的方式从 0simpleArray.length 迭代查找值。例如:

var a = [];
a[5] = "abc";
alert(JSON.stringify(a)); // alerts [null,null,null,null,null,"abc"]

因此,在此上面设置属性将对 JSON 完全不可见。

但是,使用 {} 来定义对象会使 JSON 将其视为一个对象,因此它会循环遍历对象的属性(不包括原型链中继承的属性)。通过这种方式,它可以找到您的 test1test2 属性,并成功返回您期望的内容。


1

Array 实例和其他对象之间的差异在 ECMAScript Language Specification, Edition 5.1 的第15.4节中指定。

你会发现,虽然你可以使用括号属性访问语法来访问 所有 对象引用--

objRef[propertyName]

-- Array 实例是特殊的。只有使用参数值,其字符串表示形式为小于 232-1 的无符号 32 位整数值,才能访问封装数组结构的元素,并且写入访问会影响 Array 实例的 length 属性的值。

第 15.12 节相应地指定了 JSON 对象及其 stringify 方法。


0
在Javascript中,一切都是对象,包括数组。 这就是为什么你会得到:
>> var l = [1, 2];
>> typeof l
"object"

数组的存储方式与对象相同。因此,实际上,数组只是哈希表对象。访问时提供的索引,例如

>> l[0]

它不被解释为偏移量,而是被哈希后进行搜索。

因此,数组只是带有一些内置数组方法的对象,因此您可以像处理对象一样将它们视为对象并在特定键下放置值。 但仅使用数字索引的键用于计算长度。

>> var l = []
>> l.length
0
>> l[5] = 1;
>> l.length
6
>> l
[undefined, undefined, undefined, undefined, undefined, 1]
>> l.hello = "Hello"
>> l.length
6

阅读Array - MDN以获取更多信息,以及考虑有害的关联数组为什么您不应该这样做。


并非所有的东西都是对象。存在原始值。但是并不存在“Javascript”,而是存在多个不同的ECMAScript实现。 - PointedEars

-1

你的代码语义不正确,但由于大多数JavaScript引擎对程序员非常友好,它们允许这些类型的错误

一个快速的测试用例:

var a = [];
a.b = "c";
console.log(JSON.stringify(a));//yields [], and not {"b":"c"} as you might expect

这可能是由于 JSON.stringify 的某种严格性,它仍然将 a 视为 Array。我不会过分担心这个问题。因为这是一个错误,所以在程序中不应该出现这种情况。JSLint 是一个流行的工具,可以捕获代码中的这些和更多潜在问题。

我认为这很明显:你不能在Array中声明字符串键。大多数JavaScript引擎接受它并不意味着它是“正确”的。我希望你认为这个踩一下值得。 - Halcyon
你对JavaScript的本质、ECMAScript对象以及编程语言的语法特性有严重误解。一个评论是不够的。不存在键。对象有具有名称的属性,属性名称是字符串,可以使用 [].或隐式地访问(作用域链)。JavaScript是ECMAScript实现之一。就JavaScript而言,只有两个生产质量的“实现”:SpiderMonkey和Rhino;其他如JScript则是ECMAScript实现。待续... - PointedEars
1
Array 实例是对象。一些属性名称与它们的元素相关,根据规范。在 Array 实例上使用 "字符串键" 进行属性访问可能是一个 语义 错误,如果你想要访问数组结构中的一个元素。想一想:var a = []; a["0"] = "b"; console.log(a["length"], a.length, a, a[0]); /* _以下语法_ 错误,因为 0 不是标识符: */ a.0; - PointedEars
你有一种奇怪的建设性尝试方式。你本可以简单地建议我将其更改为“语义上”。我想你的意思是,假设a是一个ArrayObject,那么a.b = "c"是有效的。因此,它在语法上是允许的,因为你可以将属性分配给对象,但从语义上讲是错误的,因为当你使用JSON.stringify时,值“c”不会出现在数组中。- 我认为争论术语并不是正确的范围。 - Halcyon
这不仅仅是关于语法和语义的问题,而是关于你不理解所涉及的编程语言。我本可以说“你根本不知道自己在说什么”,但我认为解释你错在哪里更有建设性。 - PointedEars

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接