Code前端首页关于Code前端联系我们

JSON.stringify()的性能要如何提升?

terry 2年前 (2023-09-25) 阅读数 56 #后端开发
如何提高 JSON.stringify() 的性能? ?该对象存储在localStorage中;
  • POST 请求中的 JSON 正文;
  • 处理响应文本中JSON形式的数据;
  • 即使在某些条件下,我们也会用它来实现简单的深拷贝;
  • 在一些性能敏感的情况下(比如服务器处理大量并发时)或者面临大量的stringify操作,我们希望它的性能更好更快。这也催生了一些优化的 stringify 解决方案/库。下图是它们和原生方法的性能对比: JSON.stringify()的性能要如何提升?

    绿色部分是原生的JSON.stringify()。可以看出性能比这些库要高。低得多。那么性能显着提升背后的技术原理是什么呢?

    2。比 stringify stringify

    由于 JavaScript 是一种非常动态的语言,对于一个对象类型变量来说,它包含了键名、键值。键值类型最终仅在运行时确定。因此,当您执行 JSON.stringify() 时,还有很多工作要做。在不了解其他情况的情况下,我们无法采取任何措施来显着优化。

    那么如果我们知道这个对象的键名和键值信息——也就是知道它的结构信息,这会有用吗?

    看一个例子:

    下面的对象,

    const obj = {
        name: 'alienzhou',
        status: 6,
        working: true
    };
    

    我们对它应用JSON.stringify()

    ,结果是如果我们知道这个N obj 的结构 是固定:
    • 键名保持不变
    • 键值的类型是安全的

    所以实际上我可以创建一个“自定义”字符串方法❀看看我们的❀myStringify输出

    方法:

    myStringify({
        name: 'alienzhou',
        status: 6,
        working: true
    });
    // {"name":"alienzhou","status":6,"isWorking":true}
    
    myStringify({
        name: 'mengshou',
        status: 3,
        working: false
    });
    // {"name":"mengshou","status":3,"isWorking":false}
    

    可以得到正确的结果,但只使用了类型转换和字符串连接,因此“自定义”方法可以使“stringify”更快。

    如何获得比stringify更快的stringify方法?

    1. 首先要确定物体的结构信息;
    2. 根据其结构信息为此结构中的对象创建“自定义”stringify方法。内部结果实际上是通过字符串连接生成的。
    3. 最后使用这个“自定义”方法来串接对象。 ?仅供参考,您需要为这种类型的对象创建一个“自定义” stringify 方法,这实际上是简单的属性访问和字符串连接。

      为了了解具体的实现方法,下面我以两个实现稍有不同的开源库为例简单介绍一下。

      3.1。 fast-json-stringify

      JSON.stringify()的性能要如何提升?

      下图是根据fast-json-stringify提供的benchmark结果进行的性能对比。 JSON.stringify()的性能要如何提升?

      如您所见,在大多数场景下它都有 2-5 倍的性能提升。

      3.1.1。如何定义模式

      fast-json-stringify 使用 JSON 模式验证来定义 (JSON) 对象的数据格式。模式本身定义的结构也是 JSON 格式。例如,对象

      {
          name: 'alienzhou',
          status: 6,
          working: true
      }
      

      对应的模式为:

      {
          title: 'Example Schema',
          type: 'object',
          properties: {
              name: {
                  type: 'string'
              },
              status: {
                  type: 'integer'
              },
              working: {
                  type: 'boolean'
              }
          }
      }
      

      。其模式定义规则丰富。具体使用可以参考JSON验证库Ajv。

      3.1.2。生成stringify方法

      fast-json-stringify会根据刚刚定义的schema生成实际的函数代码字符串,然后使用Function构造函数在运行时动态生成相应的stringify函数。

      在代码生成方面,主要会注入各种预定义的工具方法。这部分的各个schema都是一样的:

      var code = `
          'use strict'
        `
      
        code += `
          ${$asString.toString()}
          ${$asStringNullable.toString()}
          ${$asStringSmall.toString()}
          ${$asNumber.toString()}
          ${$asNumberNullable.toString()}
          ${$asIntegerNullable.toString()}
          ${$asNull.toString()}
          ${$asBoolean.toString()}
          ${$asBooleanNullable.toString()}
        `
      

      其次,会根据schema定义的具体内容生成stringify函数的具体代码。 。生成方法也比较简单:通过遍历schema。

      单步执行模式时,根据定义的类型,在相应的键值转换代码处插入相应的实用函数。例如,在上例中,属性name为:

      var accessor = key.indexOf('[') === 0 ? sanitizeKey(key) : `['${sanitizeKey(key)}']`
      switch (type) {
          case 'null':
              code += `
                  json += $asNull()
              `
              break
          case 'string':
              code += nullable
                  ? `json += obj${accessor} === null ? null : $asString(obj${accessor})`
                  : `json += $asString(obj${accessor})`
              break
          case 'integer':
              code += nullable
                   ? `json += obj${accessor} === null ? null : $asInteger(obj${accessor})`
                   : `json += $asInteger(obj${accessor})`
              break
          ……
      

      上面代码中的变量code存储的是最后生成的函数文本的代码字符串。由于模式定义中name的类型为string并且不为空,因此以下代码字符串将添加到codecode到:也需要处理复杂的情况例如数组和链接对象,所以省略了很多实际代码。

      之后,整个生成的code字符串看起来像这样:

      function $asString(str) {
          // ……
      }
      function $asStringNullable(str) {
          // ……
      }
      function $asStringSmall(str) {
          // ……
      }
      function $asNumber(i) {
          // ……
      }
      function $asNumberNullable(i) {
          // ……
      }
      /* 以上是一系列通用的键值转换方法 */
      
      /* $main 就是 stringify 的主体函数 */
      function $main(input) {
          var obj = typeof input.toJSON === 'function'
              ? input.toJSON()
              : input
      
          var json = '{'
          var addComma = false
          if (obj['name'] !== undefined) {
              if (addComma) {
                  json += ','
              }
              addComma = true
              json += '"name":'
              json += $asString(obj['name'])
          }
      
          // …… 其他属性(status、working)的拼接
      
          json += '}'
          return json
      }
      
      return $main
      

      最后将code

      code❀字符串传递给函数❀或构造字符串。

      // dependencies 主要用于处理包含 anyOf 与 if 语法的情况
      dependenciesName.push(code)
      return (Function.apply(null, dependenciesName).apply(null, dependencies))
      

      3.2。 Slow-json-stringify

      JSON.stringify()的性能要如何提升?

      slow-json-stringify 虽然名字很“慢”,但它实际上是一个“快”的 stringify 库(名字很厚颜无耻)。

      已知宇宙中最慢的弦化器。开个玩笑,它是最快的(:

      它的实现比前面提到的fast-json-stringify更加轻量级,思路也很聪明,同时在很多场景下使用,效率会更快比 fast-json-stringify . JSON.stringify()的性能要如何提升?JSON.stringify()的性能要如何提升?

      3.2.1 如何定义 schema

      Slow-json-stringify 的 schema 定义更加自然简单,主要是将 key value 替换为类型描述。还是在上面object 的例子中,schema 就变成了

      {
          name: 'string',
          status: 'number',
          working: 'boolean'
      }
      

      ,其实很直观。这样定义好之后,我们就可以先修改一下 schema JSON.stringify,然后“减去”所有的 type 值。最后,什么等待我们的就是将实际填写的Value直接放入scheme对应的类型声明中。

      如何操作?

      首先可以直接在schema上调用JSON.stringify()生成基本模板,借用JSON.stringify()❀方法的第二个参数来采集属性。访问路径:

      let map = {};
      const str = JSON.stringify(schema, (prop, value) => {
          const isArray = Array.isArray(value);
          if (typeof value !== 'object' || isArray) {
              if (isArray) {
                  const current = value[0];
                  arrais.set(prop, current);
              }
      
              _validator(value);
      
              map[prop] = _deepPath(schema, prop);
              props += `"${prop}"|`;
          }
          return value;
      });
      

      此时,map收集了所有属性的访问路径。同时生成的props可以拼接成匹配对应类型字符的正则表达式。例如,我们示例中的正则表达式为 /name|status|working"(string|number|boolean| undef)"|\\[(.*?)\\]/

      然后根据正则表达式依次匹配这些属性,将属性类型字符串替换为总占位符字符串"__par__"并基于"字符串:这样就得到两个chunksprops的数组。chunks包含共享的JSON字符串。例如,这两个数组如下

      // chunks
      [
          '{"name":"',
          '","status":"',
          '","working":"',
          '"}'
      ]
      
      // props
      [
          'name',
          'status',
          'working'
      ]
      

      最后,由于映射存储了协会属性名和访问路径,可以根据prop访问对象中的某个属性的值,循环遍历数组,与对应的块进行拼接即可。

      从代码角度体积和实现方法上,这种方案会更轻量、更巧妙,而且不需要通过Function、eval等动态生成或执行函数。

      4.总结

      虽然不同库的实现不同,实现高性能stringify的总体思路是相同的:

      1. 开发者为Object定义JSON schema;该库根据模式生成相应的模板方法。模板方法中,属性和值都是字符串拼接的(显然,属性访问和字符串拼接效率高很多);
      2. 最后开发者调用返回的方法对Object进行stringify就可以了。

      归根结底,它本质上是通过静态结构信息来预测优化和分析。

      提示

      最后我还是要提一下

      • 所有基准测试只能作为参考。建议您在实际业务中测试是否有性能提升以及提升了多少;
      • fast-json - stringify 中使用了函数构造函数,因此不建议直接使用用户输入作为 schema,以防止一些安全问题。

    版权声明

    本文仅代表作者观点,不代表Code前端网立场。
    本文系作者Code前端网发表,如需转载,请注明页面地址。

    发表评论:

    ◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

    热门