记一次 JSON.stringify 引发的问题

1,109 阅读2分钟

前言

目前在开发一个生成骨架屏的 webpack 插件,使用 Puppeteer 来将处理脚本通过 addScriptTag 方法来插入到页面中。

想法是想模仿 webpack 可以通过配置 plugins 来添加自定义的处理规则,问题就出在这里,通过 page.evaluate 来执行页面脚本的时候获取不到我传入的 plugins

问题

webpack plugin config

class TestPlugin {
    apply(api, ele, options) {
        console.log(api, ele, options);
    }
}
{
    ...
    port: 8899,
    plugins: [
        function test(api, ele, options) {
            console.log(api, ele, options);
        },
        new TestPlugin()
    ],
    ...
}

使用 puppeteer 的代码:

await page.evaluate(options => {
  Skeleton.genSkeleton(options);
}, this.options);

在调用 Skeleton.genSkeleton 方法的时候, options.plugins 变成了 [null, {}]。导致获取不到传递的自定义处理函数。

排查问题

首先确认 webpack 是否接受到参数

由此可以发现 wepack 已经接受到了配置的参数

那就只能是 Puppeteer 的问题了

查看了 puppetter 的官方文档。

明确说明了 evaluate 方法接受的参数必须是可以被序列化的。

然后去 github 上查看它的源码发现它使用 JSON.stringify 来进行序列化。

那么确认是 JSON.stringify 的问题了。

进一步分析问题

既然确定是 JSON.stringify 的问题了, 赶紧去看一波 JSON.stringify 的文档。 送上 JSON.stringify() 的MDN地址

由此可以发现当函数出现在非数组对象时,在序列化的过程中会被忽略,所以 plugins 第二个元素返回 {}, 当函数出现在数组对象中时,在序列化过程中会被转换成 null,所以 plugins 的第一个元素返回 null

总结

思考一下,为什么有些属性不能被 stringify 序列化。

因为 JSON 是一个通用的文本格式,和语言无关。设想如果将函数定义也 stringify 的话,如何判断是哪种语言,并且通过合适的方式将其呈现出来将会变得特别复杂。特别是和语言相关的一些特性,比如 JavaScript 中的 Symbol

ECMASCript官方也特意强调了这一点:

It does not attempt to impose ECMAScript's internal data representations on other programming languages. Instead, it shares a small subset of ECMAScript's textual representations with all other programming languages.

有兴趣的小伙伴可以了解一下如何使用 toJSON 来自定义序列化行为。