我是否已达到浏览器中JavaScript可以处理的对象大小的限制?


68

<script>在HTML的标签中嵌入了一个大型数组,就像这样(不足为奇):

<script>
    var largeArray = [/* lots of stuff in here */];
</script>

在此特定示例中,数组具有210,000个元素。这远远低于2的理论最大值31 -由4个数量级。这是有趣的部分:如果我将数组的JS源代码保存到文件中,则该文件> 44兆字节(准确地说是46573399字节)。

如果您想亲自看看,可以从GitHub下载。(其中的所有数据都是固定的,因此会重复很多。在生产环境中不会如此。)

现在,我真的不担心要提供那么多数据。我的服务器通过gzip压缩其响应,因此实际上并不需要花很长时间就能通过网络获取数据。但是,页面一旦加载,就会导致浏览器崩溃。我根本没有在IE中测试(这是一个内部工具)。我的主要目标是Chrome 8和Firefox 3.6。

在Firefox中,我可以在控制台中看到一个相当有用的错误:

Error: script stack space quota is exhausted

在Chrome中,我只是获得了悲伤标签页:

在此处输入图片说明

切入正题

  • 对于我们现代的“高性能”浏览器来说,这真的是太多数据了吗?
  • 我可以做些什么来优雅地处理大量数据?

顺便说一句,我能够在Chrome中断断续续地使用它(阅读:不会使选项卡崩溃)。我真的以为Chrome至少是由更坚固的材料制成的,但显然我错了……


编辑1

@Crayon:我并不想证明为什么我想一次将大量数据转储到浏览器中。简短的版本:要么我解决了这个(绝对不那么容易)的问题,要么我不得不解决一系列其他问题。我现在选择较简单的方法。

@various:现在,我不是特别在寻找减少数组中元素数量的方法。我知道我可以实现Ajax分页或具有什么功能,但这在其他方面为我带来了自己的一系列问题。

@Phrogz:每个元素看起来像这样:

{dateTime:new Date(1296176400000),
 terminalId:'terminal999',
 'General___BuildVersion':'10.05a_V110119_Beta',
 'SSM___ExtId':26680,
 'MD_CDMA_NETLOADER_NO_BCAST___Valid':'false',
 'MD_CDMA_NETLOADER_NO_BCAST___PngAttempt':0}

@Will:但是我有一台带有4核处理器,6 GB RAM,超过一半TB磁盘空间的计算机...我什至不要求浏览器快速执行此操作-我只是在问为它工作


编辑2

任务完成!

有了JuanGuffa的即时建议,我得以使它起作用!看来问题出在解析源代码,而不是在内存中实际使用它。

概括一下对Juan的回答的评论泥潭:我不得不将我的大型阵列分解为一系列较小的阵列,然后将Array#concat()它们分成几个阵列,但这还不够。我必须将它们放在单独的var语句中。像这样:

var arr0 = [...];
var arr1 = [...];
var arr2 = [...];
/* ... */
var bigArray = arr0.concat(arr1, arr2, ...);

对于为解决这个问题做出贡献的每个人:谢谢。第一轮就在我身上!


*除了显而易见的以外:向浏览器发送更少的数据


4
这210,000个“元素”是什么?整数?多维数组?具有许多命名属性的对象表示数据库查询的结果?
Phrogz

1
第一个问题的明显答案是:“是;如果您使浏览器崩溃,则说明数据太多”。也许您的意思是问一个过于主观的问题:“这应该是太多数据吗?”
Phrogz

4
@MattBall谢谢(我的联系阻止了我查看您的数据)。您的限制是:a)您必须加载所有数据,并且b)您必须在页面中加载所有数据。考虑到这些限制,我将尝试 var all = [ [...first 100...], [...second 100...], ... ];看看是否可以加载。如果可行,请查看是否可以将它们合并concat()。如果这不起作用...请不要再抱怨您认为浏览器应该如何处理此问题以及1.浏览器的文件错误以及2.更改您的方法。
Phrogz

3
@Phrogz:“不要抱怨”-那是个好建议:)
Matt Ball

1
@MattBall我很高兴您能按照原定的精神进行尝试。我忘了最后的笑脸。:)
Phrogz

Answers:


83

这是我尝试的方法:您说的是44MB文件。那肯定会占用超过44MB的内存,我猜想这会占用超过44MB的RAM,也许是半个演出。您是否可以减少数据直到浏览器不崩溃并查看浏览器使用了多少内存?

即使仅在服务器上运行的应用程序也可以很好地服务于不读取44MB文件并将其保存在内存中的情况。说了这么多,我相信浏览器应该能够处理它,所以让我运行一些测试。

(使用Windows 7、4GB内存)

第一次测试, 我将阵列切成两半,没有问题,使用了80MB,没有崩溃

第二次测试 我将阵列拆分为两个单独的阵列,但仍包含所有数据,使用160Mb,没有崩溃

第三次测试 由于Firefox表示已用完堆栈,因此问题可能出在它无法立即解析数组。我创建了两个单独的数组,分别为arr1,arr2,然后执行arr3 = arr1.concat(arr2); 它运行良好,仅使用了大约165MB以上的内存。

第四项测试我正在创建其中7个数组(每个22MB),并将其设置为测试浏览器限制。页面完成加载大约需要10秒钟。内存增加到1.3GB,然后又减少到500MB。所以是的,Chrome可以应付。它只是无法一次解析所有内容,因为它使用某种递归,如控制台的错误消息所示。

回答创建单独的阵列(每个阵列小于20MB),然后合并它们。每个数组应在其自己的var语句上,而不是使用单个var进行多个声明。

我仍然会考虑仅提取必要的部分,这可能会使浏览器变慢。但是,如果这是内部任务,那应该没问题。

最后一点:您没有最大的内存级别,只是最大的解析级别。


这看起来很有希望。必须尽快尝试。
马特·鲍尔

我将开始玩这个游戏。在我走错路之前:您如何准确地声明和连接数组?(1) var arr1 = [...], arr2 = [...], arr3 = [...]. bigArr = arr1.concat(arr2).concat(arr3);(2) bigArr = arr1.concat(arr2, arr3);(3) var bigArr = [...].concat([...], [...]);?还是var每个子数组都需要单独的语句,或者这无关紧要?
马特·鲍尔

这是我尝试的方法:var arr7 = arr0.concat(arr1,arr2,arr3,arr4,arr5,arr6)。我认为单独的var语句不会有任何区别。
胡安·门德斯

测试1:将阵列分成4个子阵列(每个2 ^ 15个Elts),并使用bigArr= arr0.concat(arr1).concat(arr2).concat(arr3)仍制成的Chrome barf。让我尝试一下您刚刚提出的建议...
马特·鲍尔,

我会将所有数组传递给concat(),而不是多次调用concat。我尝试了您的数据,将其分成两个数组。
胡安·门德斯

13

是的,对浏览器的要求太多了。

如果已经是数据,那么该数量的数据将是可管理的,但还不是数据。考虑到浏览器必须在检查语法汇总的同时解析大量的源代码。一旦解析为有效代码,该代码必须运行以产生实际的数组。

因此,所有数据将同时存在(至少)两个或三个版本,每个版本都有一定的开销。由于数组文字是单个语句,因此每个步骤都必须包含所有数据。

将数据分成几个较小的数组可能会使其在浏览器上更容易。


在最后一句话中,您的字面意思是做类似的事情var chunk1 = [/* first 21k elts */], chunk2 = [/* next 21k elts */], ... chunk10 = [/* last 21k elts*/];吗?
马特·鲍尔

1
@Matt Ball:是的。我什至将它们放在单独的语句中,即var chunk1 = [...]; var chunk2 = [...]; ...
2011年

是的,把它们分开。但您可以将它们连接在一起,并且浏览器还可以。问题是尝试一次解析所有内容,导致堆栈用完。见我的答案
Juan Mendes

6
这意味着要问的不是太多,如果您有礼貌地问:)
Juan Mendes

6

您真的需要所有数据吗?您不能仅使用AJAX传输当前所需的数据吗?与Google Maps类似-您无法将所有地图数据都放入浏览器的内存中,它们仅显示您当前正在查看的部分。

请记住,在浏览器的内部表示中,可以夸大40兆的硬数据。例如,JS解释器可能使用哈希表来实现该数组,这会增加额外的内存开销。另外,我希望浏览器既存储源代码又存储JS内存,仅此一项就使数据量增加一倍。

JS旨在提供客户端UI交互,而不处理数据负载。

编辑:

顺便说一句,您真的认为用户会喜欢下载40 MB的代码吗?仍然有许多用户无法访问宽带互联网。脚本的执行将被暂停,直到所有数据下载完毕。

编辑2:

我看了一下数据。该数组肯定会表示为哈希表。同样,许多项目都是对象,这将需要引用跟踪……这是额外的内存。

我想如果它是原始数据的简单向量,性能会更好。

编辑3:当然可以简化数据。其中大部分是重复的字符串,可以用某种方式将其编码为整数或某种形式。另外,我的Opera很难仅显示文本,更不用说对其进行解释了。

EDIT4:忘记DateTime对象!使用unix时代的时间戳或字符串,但不要使用对象!

EDIT5:您的处理器无关紧要,因为JS是单线程的。而且您的RAM也不重要,大多数浏览器都是32位的,因此它们不能使用太多的内存。

EDIT6:尝试将数组索引更改为连续整数(0、1、2、3 ...)。这可能会使浏览器使用更有效的数组数据结构。您可以使用常量来有效地访问数组项。这将减少大量的数组大小。


为Edit4 +1;以您可以压缩的格式带来数据,然后在客户端将其扩展为对象。
Phrogz

...并且不要一次全部扩展,一次只扩展几条记录。尽管我不确定浏览器的内存管理水平如何,但这可能无济于事。
马捷Zábský

玩弄日期-更改为时间戳(自纪元以来的毫秒数)-没有帮助。不过,这也许可以缓解问题的一小部分,并且与其他调整一起使用很有用。
马特·鲍尔

即使使用宽带,我也不想加载并保留大约40MB(原始!)的垃圾:PI不知道真正的内存使用量是多少。

@Matt:我想知道消除Date对象并没有太大区别的事实是否证明了@Guffa的答案可能是有价值的。
user113716 2011年

5

尝试使用Ajax作为JSON页面检索数据。我不知道确切的大小,但是我已经能够通过这种方式将大量数据提取到Google Chrome中。


就像我说的那样,虽然可能不够清晰,但我并不是在寻找有关如何一次将更少的数据转储到页面中的建议。
Matt Ball

1
我不建议加载较少的数据,而是建议以其他方式加载。不是作为脚本的一部分,而是作为单独的JSON文档。这取决于浏览器如何实现JSON解析,但是Chrome至少在没有'eval'-ing脚本的情况下做到了这一点,它可以更高效地解析JSON。
Bitsplitter

因此,基本上,加载相同的确切信息量,但是使用Ajax而不是页面的其余部分?这可能值得一试。
马特·鲍尔

3

使用延迟加载。拥有指向数据的指针,并在用户询问时获取它。

这项技术已在各种地方用于管理数百万条数据记录。

[编辑]

我找到了想要的东西。在jqgrid中虚拟滚动。这是50万条记录的延迟加载。


您介意对此进行扩展吗?您是指使用Ajax进行延迟加载吗?
马特·鲍尔

1
@MattBall在分页数据和逐段加载数据以及进行预测性延迟加载之间有一个重要的区别,在这种情况下,您在用户需要数据之前先在后台偷偷加载,并假装用户没有加载时间,但实际上并不那么麻烦。
雷诺斯2011年

2

我会尝试将其作为一个大字符串,在每个“项目”之间使用分隔符,然后使用split,例如:

var largeString = "item1,item2,.......";
var largeArray = largeString.split(",");

希望字符串不会很快耗尽堆栈。

编辑:为了测试它,我创建了一个虚拟数组,其中包含200,000个简单项目(每个项目为一个数字),Chrome立刻加载了它。2,000,000件?几秒钟,但没有崩溃。6,000,000个项目阵列(50 MB文件)使Chrome加载了大约10秒钟,但无论哪种方式都没有崩溃。

因此,这使我相信问题不在于数组本身,而在于它的内容..将内容优化为简单的项目,然后“即时”解析它们,它应该可以工作。


3
您认为这有什么帮助?我最终会得到完全相同的最终结果,但要花更多的时间才能到达那里。
Matt Ball

1
我的理论是,JS可以处理大字符串,并且在“运行时”而不是预定义的条件下创建数组将占用更多CPU,但堆栈内存更少。恐怕只有理论,但IMO值得尝试。
影子巫师正在给

2
你的理论没有任何意义,我
胡安·门德斯

@Juan我希望尽快对此进行测试,以证明或反驳我的理论。:)
影子巫师正在为

2
@Shadow向导,请参阅我的答案。在爆炸了您的方法之后,它可能实际上可以工作,因为字符串解析可能不使用递归(堆栈密集型)方法,而解析json数组显然使用了递归(FF的错误消息与堆栈大小有关)。我现在感到谦虚。
Juan Mendes
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.