【问题标题】:How are JavaScript arrays represented in physical memory?JavaScript 数组如何在物理内存中表示?
【发布时间】:2013-12-17 17:35:48
【问题描述】:

据我了解,我可以将混合数据存储在 JavaScript 数组中,也可以将数组中的任何元素更改为其他类型。解释器如何跟踪任何元素在物理内存中的位置。另外,如果我将元素更改为更大的数据类型,如何防止覆盖下一个元素中的数据。

我假设数组只存储对实际对象的引用,而基元在放置在数组中时被包装在幕后。

假设是这种情况,如果我对原始变量有不同的句柄并更改存储在数组中的值是否保持同步?

我知道我可能已经回答了我自己的问题,但我不确定,也找不到有关此事的任何信息。

【问题讨论】:

  • 不同的 JavaScript 实现可能有不同的方法。
  • 您不能修改原语。分配一个数组元素永远不会改变一些其他变量或数组。 (如果该数组是另一个数组(或多个其他数组)的元素,则更改将通过其他数组可见,但是您永远不会有x=3,在某处分配给数组,并且然后突然看到x==4。)
  • 这就是你的意思jsfiddle.net/2xBdx/1
  • JS 中的数组不同于其他语言中的数组。他们没有那些优化来自拥有一个连续的内存部分和一个简单的乘法运算来获得项目 i 所需的内存地址。在 JS 中,数组是真正的对象 - 散列,如果特定的 JS 实现有优化,以及什么样的优化,取决于。最坏的情况是索引只是散列键,因此索引 1 处的第二个数组项使用对象中的散列键“1”定位。优点:稀疏数组(巨大而实际值很少)。缺点:大多数(小)数组(除非内部优化)。
  • @Brad 的简单评论应该是这个问题的公认答案。

标签: javascript arrays dynamic weakly-typed


【解决方案1】:

通常,数组分配一个固定长度的连续内存块。但是,在 Javascript 中,数组是具有特殊构造函数和访问器方法的 Object 类型。

也就是说,像这样的语句:

var arr = new Array(100000);

不分配任何内存!实际上,它只是简单地设置数组中长度属性的值。当你构造一个数组时,你不需要声明一个大小,因为它们会自动增长。所以,你应该改用这个:

var arr = [];

Javascript 中的数组是稀疏的,这意味着并非数组中的所有元素都可能包含数据。换句话说,只有实际包含数据的元素存在于数组中。这减少了阵列使用的内存量。这些值是通过键而不是偏移量来定位的。它们只是一种方便的方法,并不打算用于复杂的数值分析。

Javascript 中的数组没有类型,因此元素的值可以是对象、字符串、数字、布尔值、函数或数组。数组和对象的主要区别在于 length 属性的值大于数组中最大的整数键。

例如:

您可以创建一个空数组并在索引 0 和索引 99 处添加两个元素。长度为 100,但数组中的元素数为 2。

var arr = [];
arr[0] = 0;
arr[99] = {name: "John"};
console.log(arr.length); // prints 100
arr; // prints something like [0, undefined × 98, Object { name: "John"}]

直接回答您的问题:

问。据我了解,我可以将混合数据存储在 JavaScript 数组中,也可以将数组中的任何元素更改为其他类型。解释器如何跟踪任何元素在物理内存中的位置?另外,如果我将元素更改为更大的数据类型,如何防止覆盖下一个元素中的数据?

A.如果您已经阅读了我上面的 cmets,您现在可能已经知道这一点。在 Javascript 中,数组是一种 Hashtable 对象类型,因此解释器不需要跟踪物理内存,并且更改元素的值不会影响其他元素,因为它们没有存储在连续的内存块中。

--

问。我假设数组只存储对实际对象的引用,当放置在数组中时,原语被包装在幕后。假设是这种情况,如果我对原始变量有不同的句柄并更改存储在数组中的值是否保持同步?

A.不,原语没有被包装。更改分配给数组的原语不会更改数组中的值,因为它们是按值存储的。另一方面,对象是通过引用存储的,因此更改对象值将反映该数组中的更改。

您可以尝试以下示例:

var arr = [];
var obj = { name: "John" };
var isBool = true;

arr.push(obj);
arr[1] = isBool;

console.log(arr[0]); // print obj.name
console.log(arr[1]); // print true

obj.age = 40;        // add age to obj
isBool = false;      // change value for isBool

console.log(arr[0]); // value here will contain age
console.log(arr[1]); // value here will still be true

另外,请注意,当您以以下两种方式初始化数组时,它具有不同的行为:

var arr = new Array(100);
console.log(arr.length);        // prints 100
console.log(arr);               // prints []

var arr2 = new Array(100, 200);
console.log(arr2.length);       // prints 2
console.log(arr2);              // prints [100, 200]

如果你想使用 Javascript 数组作为连续的内存块,你应该考虑使用TypedArray。 TypedArray 允许您将一块内存分配为字节数组并更有效地访问原始二进制数据。

您可以通过阅读ECMA-262 spec (ver 5.1) 了解更多关于 Javascript 的复杂性。

【讨论】:

  • 很好的答案,但我认为值得一提的是,这里有很多关于实现定义的事情。例如,在 SpiderMonkey(早期 Mozilla JS 实现的骨干)中,数组被实现为 jsval 的密集 C 数组,直到您对它们执行某些操作,此时引擎可以将它们切换到您下面的对象。
  • 你是对的。谢谢你提出来。我只是在考虑这个并想编辑答案,但可能在上午。
  • 这个 jsperf 似乎与在 JS 中预分配数组是不可能的/有益的想法相矛盾:jsperf.com/array-allocation-push-vs-new-array-size-assignment
  • 这是否意味着javascript中的数组没有缓存局部性?
  • 这很棒。如果有人知道如何使用 TypedArray 作为 V8 的外部存储器,请告诉我。
【解决方案2】:

这里有一些值得深思的地方。我制作了一些 JavaScript 引擎实现的 a jsperf to test a simple array optimization

测试用例创建了两个数组,每个数组有一百万个元素。 a 数组只包含数字; b 数组包含相同的数字,但第一个元素是对象:

var a = [ 0 ], b = [ { valueOf: function() { return 0; } } ];
for( var i = 1;  i < 1000000;  ++i ) {
    a[i] = b[i] = i;
}

b 数组的第一个元素中对象的valueOf 属性返回0,因此算法将与第一个数组相同。

那么这两个测试只是简单地将两个数组的所有值相加。

快速数组:

var x = 0;
for( var i = 0;  i < 1000000;  ++i ) {
    x += a[i];
}

慢数组:

var x = 0;
for( var i = 0;  i < 1000000;  ++i ) {
    x += b[i];
}

从 jsperf 中的测试结果可以看出,在 Chrome 中,数值数组大约快 5 倍,在 Firefox 中,数值数组大约快 10 倍,在 IE 中大约快 2 倍。

这并没有直接揭示用于数组的内部结构,但它很好地表明两者之间有很大的不同。

【讨论】:

  • 你在开玩笑吗?慢速数组添加一个带有数字的对象 1000000 次!这就是区别!你应该在 jsperf 循环中设置 var i = 1
  • @yangguang1029 不,它没有。它曾经在第一次加法时将对象转换为一个数字。
【解决方案3】:

接受的答案是这样的

[一个数组构造]不分配任何内存!

这不一定是真的。将数组存储为对象中的无序键值对非常低效,因为每次查找都是一次搜索。即使对条目进行了排序,它仍然会分配比需要更多的内存。因此,许多引擎以几种不同的方式表示数组以保护内存和优化性能。这也可以在 Michael Geary 的示例中看到,仅包含数字的数组得到优化 (details for V8)。有时引擎可能会决定为新创建的数组分配一些空槽,如果底层表示具有固定大小并且不能那么容易地缩放(例如数组列表)。

【讨论】:

    【解决方案4】:

    当一个值被分配给数组范围之外的正索引时, 然后数组将此值存储在指定索引和所有其他索引处 在它为空之前。数组的长度也变为'index+1'。

    当使用负值时,数组将元素存储为 键值对,其中负索引是键和元素 插入的是值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-14
      • 2015-07-19
      • 2016-12-28
      • 2020-05-18
      相关资源
      最近更新 更多