本篇是为了解释在 WebWorker 中,数据在线程之间的传递上为什么是一个序列化和反序列化的过程。
关于更多:什么 Js
JavaScript 应用程序,尤其是网络应用程序,通常使用对象来表示状态或其他数据。通常的,我们在在开发中,会经常遇到几千字节大小的数据对象,特别是在使用框架进行开发构建网络应用程序中。
不幸的是,网络应用程序通常依赖于这些数据进行初始化渲染。这种情况下,这个大型的 JavaScript 对象最终会处出现在关键路径上。因此用户只能盯着一个空白的屏幕,直到所有数据被 Js 引擎加载、解析、编译和执行。
那么开发人员如何才能使应用更快? 一种方式是使用服务端渲染 SSR:我们会通过提供包含处理数据形式的纯 HTML,因此应用程序的初始化加载不需要 JavaScript。(可以通过 Node,Next.js、Nuxt.js 轻量级服务端渲染库来实现。) 这样,从而让我们避免了使用大型的 JavaScript 对象。
如果我们不能使用服务端来做渲染,那我们还能有其他的方式来提高性能吗?
JSON.parse 构建
大多数情况下,此关键数据不包含任何无法在 JSON 中明确表示的内容:不支持 Date 对象、BigInts、Maps、Sets等。 在这种情况下,则可以选择将对象序列化为 JSON,将其转换为 JavaScript 字符串文字,然后再将其传递给内置的 JSON.parse 函数,而不是在 JavaScript 源代码中使用对象文字。
这种方式产生了一个等效的对象:
const obj = {
activity: "bengcaca",
des: "balabala"
}
const json = JSON.parse('{"activity": "bengcaca", "des": "balabala"}')
我们可以看到在结果上,两者的一致性:
JSON.parse 会更快?
实际上,JSON 的方式会要快的多。这或许让我们感到怀疑,并且有些违反我们工作习惯,且 JSON.parse 像是附加的间接层并未脱离对 JavaScript 的使用,但真的是这样的?那为什么还要说 JSON.parse 要快的多?
一部分原因是由于 JavaScript 引擎对 JSON 示例非常容易识别、解析。
JSON.parse('{"activity": "bengcaca", "des": "balabala"}')
对于 JavaScript 解析器,这段代码不过是一个带有单参数的表达式程序,整个数据也只是一个被标记的字符串字面量。
另一方面,对于 object 来说等效的对象字面量则由更多的标记组成,由多个标记组成。每一个属性名称都是一个标识符标记或类似字符串的字面量。在这种情况下,每一个值都是一个数字字面量 (实际上它可以是任意一种类型数据)。它们可以是嵌套的对象或数组,具有自己的属性和值,这种情况下则会有更多的标记。
const obj = {
activity: "bengcaca",
des: "balabala"
}
因此与 JSON.parse 相比,Js 解析器需要付出更多的努力,才可以正确的标记脚本。 Js 对象字面量难以扫描解析的另一个原因是:你无法提前知道它们是对象字面量,又或是其他格式的程序。
假如你是一个解析器,你正在一个一个字符的解析源代码:
- 在 Json 中,如果你看到左大括号,则只会存在两种可能性:这是对象的开头或者是无效Json。
- 在Js 对象字面中,情况则是复杂的。左大括号可以是对象字面量的开始,也可以其他的一些东西。
我们来看一些代码
const num1 = 1;
const num2 = ({ x }
此时我们对括号内的 x 是无法确定的,它具体是第一行 x 变量的绑定还是这样:const y = ({ x }={ x : 0 }) 的数据解构。
事实上,我们不通过后续的代码更不会知道,回答这个问题。
在下面这个场景中,他还可以是这样的:const y = ({ x }) => x。
这样说是想要表达:解析 Js 是一个很复杂的事情,它的语法是与上下文相关的。显然的 Json 就不存在这样的问题,我们在解析上会轻松的多。
现在我们来看作为中间层的 JSON.parse 方法。事实上,只要 JSON 字符串只计算一次, JSON.parse 方法就比 JavaScript 对象文字快得多,尤其是对于冷加载。
对大量数据使用纯对象文本时,还有一个额外的风险:它们可能会被解析两次。
- 第一次传递发生在预解析文本时;
- 当文本被延迟解析时,会有第二次传递; 第一次是无法避免的,这是机制的问题。幸运的是对于第二次的传递,可以将对象数据放在顶层或者通过 IIFE 的执行方式来避免。
回到我们一开始的问题,这也是为什么,特别的对于大对象使用 JSON.parse 比使用 Js 对象字面量快的多。 这些知识可用于提高 Web 应用的启动性,这一点在很多打包工具上都有体现。
究竟快了多少?
数据会随着时间受到不同因素的影响。
针对冷负载上的 8mb 有效负载进行测量,在 v8 和 chrome 中 JSON.parse 的速度是对象的文字的 1.7 倍。在 safari 会达到 2 倍的差距。
Henrik Joreteg 将此优化应用于 Redux 中,在结果上交互时间提高了 18% ,应用程序的 lighthouse 的性能得分由 87 -> 95。
与等效的 JavaScript 对象字面量相比, JSON.parse('…') 的解析、编译和执行速度要快得多——不仅在 V8 中(速度为 1.7×),而且在所有主要的 JavaScript 引擎中也是如此。
最后:
我们可以如何开始使用该优化?
通常不介意手动去做此操作,在源码中,我们应该依旧使用对象文字得以保证数据的可读性。
相反的,我们可以通过工具来将大型对象文字转换为 JSON.parse 作为构建时的优化。如果代码库在使用 Json 模块,其实目前的一些打包工具,例如:WebPack、Rollup、Browserify等已经应用了 JOSN.parse。
同样的,我们也可以通过 Babel 插件 将转换应用于其他代码。