JavaScript数组的进化

JavaScript 数组不是连续(contiguous)的, 其实现类似哈希映射(hash-maps)或字典(dictionaries) 。 觉得这有点像是一门 B 级语言, 数组实现根本不恰当 。
工具/原料JavaScript、电脑操作系统
方法/步骤1为什么说 JavaScript 数组不是真正的数组
数组是一串连续的内存位置, 用来保存某些值 。 注意重点, “连续”(continuous, 或 contiguous), 这很重要 。

JavaScript数组的进化

文章插图

2上图展示了数组在内存中存储方式 。 这个数组保存了 4 个元素, 每个元素 4 字节 。 加起来总共占用了 16 字节的内存区 。
假设声明了 tinyInt arr[4];, 分配到的内存区的地址从 1201 开始 。 一旦需要读取 arr[2], 只需要通过数学计算拿到 arr[2] 的地址即可 。 计算 1201 + (2 X 4), 直接从 1209 开始读取即可 。

JavaScript数组的进化

文章插图

3JavaScript 中的数据是哈希映射, 可以使用不同的数据结构来实现, 如链表 。 所以, 如果在 JavaScript 中声明一个数组 var arr = new Array(4), 计算机将生成类似上图的结构 。 如果程序需要读取 arr[2], 则需要从 1201 开始遍历寻址 。
以上急速 JavaScript 数组与真实数组的不同之处 。 显而易见, 数学计算比遍历链表快 。 就长数组而言, 情况尤其如此 。
JavaScript 数组的进化
JavaScript 这门语言也进化了不少 。 从 V8、SpiderMonkey 到 TC39 和与日俱增的 Web 用户, 巨大的努力已经使 JavaScript 成为世界级必需品 。 一旦有了庞大的用户基础, 性能提升自然是硬需求 。
【JavaScript数组的进化】现代 JavaScript 引擎是会给数组分配连续内存的 —— 如果数组是同质的(所有元素类型相同) 。 优秀的程序员总会保证数组同质, 以便 JIT(即时编译器)能够使用 c 编译器式的计算方法读取元素 。
想要在某个同质数组中插入一个其他类型的元素, JIT 将解构整个数组, 并按照旧有的方式重新创建 。
如果代码写得不太糟, JavaScript Array 对象在幕后依然保持着真正的数组形式, 这对现代 JS 开发者来说极为重要 。
数组跟随 ES2015/ES6 有了更多的演进 。 TC39 决定引入类型化数组(Typed Arrays), 于是我们就有了 ArrayBuffer 。
ArrayBuffer 提供一块连续内存供随意操作 。 直接操作内存还是太复杂、偏底层 。 于是便有了处理 ArrayBuffer 的视图(View) 。 目前已有一些可用视图, 未来还会有更多加入 。

JavaScript数组的进化

文章插图

4了解更多关于类型化数组(Typed Arrays)的知识, 请访问 MDN 文档 。
高性能、高效率的类型化数组在 WebGL 之后被引入 。 WebGL 工作者遇到了极大的性能问题, 即如何高效处理二进制数据 。 可以使用 SharedArrayBuffer 在多个 Web Worker 进程之间共享数据提升性能 。
从简单的哈希映射到现在的 SharedArrayBuffer
旧式数组 vs 类型化数组:性能
前面已经讨论了 JavaScript 数组的演进, 现在来测试现代数组到底能带来多大收益 。

JavaScript数组的进化

文章插图

5旧式数组和 ArrayBuffer 的性能不相上下, 现代编译器已经智能化, 能够将元素类型相同的传统数组在内部转换成内存连续的数组 。 第一个例子正是如此 。 尽管使用了 new Array(LIMIT), 数组实际依然以现代数组形式存在 。
接着修改第一例子, 将数组改成异构型(元素类型不完全一致)的, 来看看是否存在性能差异 。

推荐阅读