JavaScript 数组如何在物理内存中表示?

IT技术 javascript arrays dynamic weakly-typed
2021-02-03 07:24:58

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

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

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

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

4个回答

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

这意味着,像这样的语句:

var arr = new Array(100000);

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

var arr = [];

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

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

例如:

您可以创建一个空数组并在索引 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 数组中存储混合数据,也可以将数组中的任何元素更改为其他类型。解释器如何跟踪任何元素在物理内存中的位置?另外,如果我将元素更改为更大的数据类型,如何防止覆盖下一个元素中的数据?

答:如果您已经阅读了我上面的评论,您现在可能已经知道了。在 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 数组作为连续的内存块,您应该考虑使用TypedArrayTypedArray 允许您将内存块分配为字节数组并更有效地访问原始二进制数据。

您可以通过阅读ECMA-262 规范(5.1 版)来了解有关 Javascript 复杂性的更多信息

这很棒。如果有人知道如何使用 TypedArray 作为 V8 的外部存储器,请告诉我。
2021-03-15 07:24:58
这个 jsperf 似乎与在 JS 中预分配数组是不可能的/有益的想法相矛盾:jsperf.com/array-allocation-push-vs-new-array-size-assignment
2021-03-16 07:24:58
你说的对。谢谢你提出这个问题。我只是在想那个并想编辑答案,但可能是在上午。
2021-03-18 07:24:58
很好的答案,但我认为值得注意的是,这里有很多关于实现定义的事情的争论。例如,在 SpiderMonkey(早期 Mozilla JS 实现的支柱)中,数组被实现为 jsval 的密集 C 数组,直到您对它们执行某些操作,此时引擎可以将它们切换到您下面的对象。
2021-03-27 07:24:58
这是否意味着 javascript 中的数组没有缓存位置?
2021-04-01 07:24:58

这是一些值得深思的食物。我做了一个 jsperf 来测试一些 JavaScript 引擎实现的简单数组优化

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

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

数组valueOf第一个元素中对象属性b返回,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
2021-03-15 07:24:58
@yangguang1029 不,它没有。它在第一次添加时将对象转换为一个数字。
2021-04-01 07:24:58

接受的答案说

【一个数组构造】不分配任何内存!

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

当一个值被分配给数组范围之外的正索引时,数组将该值存储在指定的索引处,并且在它为空之前的所有其他索引处。数组的长度也更改为“index+1”。

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