我在浏览器中处理的JavaScript对象大小已经达到了极限吗?

74

我正在将一个大数组嵌入到我的HTML的<script>标签中,像这样(没有什么意外):

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

在这个特定的例子中,该数组有210,000个元素。这远低于理论最大值2的31次方,少了4个数量级。有趣的是:如果我将数组的JS源代码保存到文件中,那么该文件的大小将超过44兆字节(确切地说是46,573,399字节)。
如果您想亲自查看,可以从GitHub上下载它。(其中所有数据都是固定的,因此其中许多数据是重复的。这在生产环境中不会发生。)
现在,我真的不担心服务这么多数据。我的服务器对其响应进行gzip压缩,因此获取数据并不需要太长时间。但是,页面一旦加载后,就会导致浏览器崩溃。我没有在IE中进行测试(这是一个内部工具)。我的主要目标是Chrome 8和Firefox 3.6。
在Firefox中,我可以在控制台中看到一个相当有用的错误:

错误:脚本堆栈空间配额已耗尽

在Chrome中,我只会得到一个悲伤的标签页。

enter image description here

言归正传

  • 我们现代的“高性能”浏览器真的无法处理这么多数据吗?
  • 我有什么办法可以优雅地处理这么多数据吗?*

顺便说一句,我在 Chrome 上时而成功(也就是没有崩溃标签页),时而失败。我真的以为 Chrome 至少还算坚强,但显然我错了...


编辑1

@Crayon:我不是在解释为什么我想一次性将这么多数据转储到浏览器中。简而言之:要么我解决这个问题(承认这并不容易),要么我必须解决一系列其他问题。目前我选择更简单的方法。

@各位:目前,我并不特别寻求实际减少数组中元素数量的方法。我知道我可以实现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内存和超过0.5 TB磁盘空间的电脑......我甚至不要求浏览器快速运行,我只是希望它能正常工作 起码!☹


编辑2

任务完成!

JuanGuffa的准确建议下,我成功地让它工作了!问题似乎只是在解析源代码上,而不是实际在内存中处理它。

总结一下对Juan答案的评论混乱:我必须将我的大数组分成一系列较小的数组,然后Array#concat()它们,但这还不够。我必须将它们放入单独的var语句中。像这样:

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

对于所有贡献解决方案的人:谢谢你们。第一轮由我请客!


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


4
这些“210,000个元素”指的是什么?整数?多维数组?具有许多命名属性的对象,代表数据库查询的结果? - Phrogz
4
@MattBall 谢谢(我的网络连接导致我无法看到你的数据)。你面临的限制是a)必须加载所有数据,和b)必须在页面上全部加载。考虑到这些限制,我建议你_尝试_使用 var all = [ [...前100个...], [...后100个...], ... ]; 看看是否可以加载。如果这样做可以成功,再尝试使用 concat() 方法将它们合并。如果这种方法行不通......不要抱怨浏览器应该如何处理这个问题,而是1.向浏览器报告错误2.改变你的方法。 - Phrogz
4
@Phrogz说的“quit whining”是个很好的建议 :)。 - Matt Ball
2
@MattBall 我很高兴你以预期的方式接受了它。我忘记在结尾加上笑脸了。 :) - Phrogz
1
@Matt Ball:第一轮就靠你了,是吧?等我去波士顿看望家人的时候,我会记得这件事的。 - Ruan Mendes
显示剩余8条评论
6个回答

88

以下是我的建议:你说这是一个44MB的文件。它肯定需要超过44MB的内存,我猜测可能需要半个gig以上的RAM。你可以尝试减少数据量,直到浏览器不崩溃,并查看浏览器使用了多少内存?

即使是仅在服务器上运行的应用程序也最好不要读取一个44MB的文件并将其保存在内存中。话虽如此,我认为浏览器应该能够处理它,所以让我进行一些测试。

(使用Windows 7,4GB内存)

第一个测试 我把数组减半,没有问题,使用80MB,没有崩溃

第二个测试 我把数组拆分成两个单独的数组,但仍包含所有数据,使用160MB,没有崩溃

第三个测试 由于Firefox说它已经用完了栈,所以问题可能是它无法一次解析整个数组。我创建了两个单独的数组arr1、arr2,然后做了arr3 = arr1.concat(arr2); 它运行得很好,只增加了一点点内存,大约165MB。

第四个测试 我创建了7个这样的数组(每个数组22MB),并将它们连接起来以测试浏览器的限制。页面完成加载需要大约10秒钟。内存升高到1.3GB,然后回落到500MB左右。所以,Chrome可以处理它。它只是无法一次性解析它,因为它使用某种递归,可以通过控制台的错误消息注意到。

答案 创建单独的数组(每个小于20MB),然后将它们串联起来。每个数组应该在自己的变量声明中,而不是使用单个var进行多个声明。

我仍然建议只获取必要的部分,这可能会使浏览器变得缓慢。但如果这是一个内部任务,那么这应该没问题。

最后一点:你没有达到最大内存水平,只是达到了最大解析级别。


这看起来非常有前途。必须尽快尝试。 - Matt Ball
我即将开始尝试这个。在我走错路之前:你是如何声明和连接数组的?**(1)** var arr1 = [...], arr2 = [...], arr3 = [...]. bigArr = arr1.concat(arr2).concat(arr3); 还是 (2) bigArr = arr1.concat(arr2, arr3); 还是 (3) var bigArr = [...].concat([...], [...]);?或者我需要为每个子数组单独声明一个 var 语句,或者这些都不重要? - Matt Ball
这是我会尝试的代码:var arr7 = arr0.concat(arr1,arr2,arr3,arr4,arr5,arr6)。我认为单独的变量声明不会有任何区别。 - Ruan Mendes
太棒了,太棒了,太棒了。我简直不敢相信这真的起作用了,谢谢。最终的修复方法是声明每个子数组都有自己的var语句,就像Guffa建议的那样(请参见他的评论)。 - Matt Ball
使用 Web Worker 处理繁重任务,就不会出现 UI 卡顿。 - Lukas Liesis
显示剩余2条评论

13

是的,对于浏览器来说这也太难了。

如果这些数据本身就是数据,那么管理这些数据的数量是可行的,但它现在不是数据。请考虑浏览器必须解析那个巨大的源代码块,并检查其全部语法是否正确。一旦解析成有效的代码,代码就必须运行以产生实际的数组。

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

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


你的最后一句话是指像这样做吗:var chunk1 = [/* 前 21k 个元素 */], chunk2 = [/* 接下来的 21k 个元素 */], ... chunk10 = [/* 最后的 21k 个元素*/]; - Matt Ball
1
@Matt Ball:是的。我甚至会将它们放在不同的语句中,即 var chunk1 = [...]; var chunk2 = [...]; ... - Guffa
是的,将它们分开。但是您可以将它们连接在一起,浏览器也可以处理。问题在于尝试一次解析所有内容时,会耗尽堆栈。请参见我的答案。 - Ruan Mendes
7
如果你礼貌地请求,这并不过分。 - Ruan Mendes

6
你是否真的需要所有的数据?不能使用AJAX仅流式传输当前所需的数据吗?类似于Google Maps-你无法将所有地图数据都适应浏览器内存中,他们只显示您当前正在查看的部分。
请记住,40兆硬数据在浏览器的内部表示中可能会膨胀得更多。例如,JS解释器可以使用哈希表来实现数组,这将增加额外的内存开销。此外,我预计浏览器将存储源代码和JS内存,仅这一点就会使数据量翻倍。
JS旨在提供客户端UI交互,而不是处理大量数据。
编辑:顺便问一下,你真的认为用户会喜欢下载价值40兆字节的代码吗?仍然有许多用户没有宽带互联网接入。并且在下载所有数据之前,脚本的执行将被暂停。
编辑2:我查看了数据。该数组肯定将被表示为哈希表。此外,许多项目都是对象,这将需要引用跟踪...即额外的内存。
我想如果它是简单的原始数据向量,性能会更好。
编辑3:数据肯定可以简化。其中大部分是重复的字符串,可以通过某种方式编码为整数或其他内容。此外,我的Opera仅显示文本就有问题,更别提解释了。
编辑4:忘记DateTime对象吧!使用Unix纪元时间戳或字符串,但不要使用对象!
编辑5:您的处理器并不重要,因为JS是单线程的。您的RAM也不重要,大多数浏览器都是32位的,因此它们无法使用太多内存。
编辑6:尝试将数组索引更改为连续的整数(0、1、2、3...)。这可能会使浏览器使用更有效的数组数据结构。您可以使用常量有效地访问数组项。这将大幅减少数组大小。

对于Edit4加1;尽可能以压缩格式传输数据,然后在客户端将其展开为对象。 - Phrogz
不要一次展开所有内容,只展示少量记录。虽然我不确定浏览器的内存管理有多好,所以这可能没有帮助。 - Matěj Zábský
玩弄日期 - 转换为时间戳(自纪元以来的毫秒数)- 没有帮助。虽然如此,也许它可以缓解问题的一小部分,并且与其他微调结合使用会很有用。 - Matt Ball
@Matt:我也是这样想的,但TextMate已经花了过去10分钟尝试打开我下载的文件。等等……我看到滚动条了!也许就快了。 - user113716
@Patrick: 是的,我试着用Notepad++打开它,可惜不行。但你猜怎么着——这里有一个相关的SO问题!对我来说,010 Editor能够解决这个问题。 - Matt Ball
显示剩余2条评论

5

尝试使用Ajax作为JSON页面检索数据。我不知道确切的大小,但我已经能够以这种方式将大量数据拉入Google Chrome。


就像我之前说的那样,虽然可能没有表达清楚,但我并不是在寻求有关如何一次性减少页面数据量的建议。 - Matt Ball
1
我并不建议加载更少的数据,而是建议以不同的方式加载它。不要将其作为脚本的一部分,而是作为单独的JSON文档加载。这取决于浏览器如何实现JSON解析,但至少Chrome可以在不使用'eval'脚本的情况下更有效地解析JSON。 - Bitsplitter
所以基本上,加载相同数量的信息,但使用Ajax而不是页面的其余部分?这可能值得一试。 - Matt Ball

3
使用延迟加载。有指向数据的指针,当用户要求时获取数据。
这种技术在各个地方用于管理数百万条数据记录。
[编辑]
我找到了我要找的东西。在 jqgrid中进行虚拟滚动。这是500k条记录被延迟加载。

你介意详细解释一下吗?你是指使用Ajax进行延迟加载吗? - Matt Ball
1
@MattBall,分页数据和逐步加载数据之间有一个重要的区别,还有一种预测性的惰性加载方式,在用户需要数据之前偷偷地在后台加载,并假装用户没有加载时间,但实际上只是不显眼。 - Raynos

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,但堆栈内存较少。只是我的理论,但我认为值得一试。 - Shadow The Spring Wizard
2
你的理论对我来说没有意义。 - Ruan Mendes
3
@Shadow Wizard,请看我的回答。虽然我批评了你的方法,但实际上它可能有效,因为字符串解析可能不使用递归(堆栈密集型)方法,而解析JSON数组显然需要递归(火狐的错误消息是关于堆栈大小的)。现在我感到很自惭形秽。 - Ruan Mendes
@Shadow: 虽然您的想法很有道理,但是 OP 需要的是数组内部的对象,而不是字符串。 - Ruan Mendes
显示剩余4条评论

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