简易版本JSON.stringify的实现及其六大特性详解

编辑: admin 分类: javascript 发布时间: 2021-11-17 来源:互联网
目录
  • 前言
  • JSON.stringify六大特性
    • 特性一
    • 特性二
    • 特性三
    • 特性四
    • 特性五
    • 特性六
  • 手动实现stringify
    • 总结

      前言

      JSON.stringify是一个使用非常高频的API,但是其却存在一个特性,我们在使用的过程中需要留意这些特性以避免为代码程序埋雷,那么接下来便一起动手实现一个简易版本的jsonStringify函数

      JSON.stringify六大特性

      特性一

      布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值

      现在有这么一个对象:

      const obj = {
          bol: new Boolean(true),
          num: new Number(1),
          str: new String(1)
      }
      

      利用typeof检测obj各个属性的数据类型

      typeof obj.bol; // object
      typeof obj.num; // object
      typeof obj.str; // object
      

      将其序列化stringify之后

      JSON.stringify(obj); // {"bol":true,"num":1,"str":"1"}
      

      此时再将其解析parse进行各个属性的数据类型

      const stringifyObj = JSON.parse(JSON.stringify(obj));
      typeof stringifyObj.bol; // boolean
      typeof stringifyObj.num; // number
      typeof stringifyObj.str; // string
      

      特性二

      NaN、Infinity、-Infinity以及null在序列化stringify时都会被当作null

      const obj = {
          nan: NaN,
          infinity: Infinity,
          null: null,
      };
      
      JSON.stringify(obj); // {"nan":null,"infinity":null,"null":null}
      

      特性三

      对象在序列化的时候,若是其存在toJSON函数,这个函数返回的值就是整个对象序列化后的结果

      const obj = {
          nan: NaN,
          infinity: Infinity,
          null: null,
          toJSON() {
              return "拥有toJSON函数";
          },
      };
      
      JSON.stringify(obj); // "拥有toJSON函数"
      

      可以看到序列化之后的数据仅存在toJSON函数的返回值,其余数据全部忽略

      ⚠️:Date数据会被正常序列化,因为Date上部署了toJSON函数,可以通过控制台打印Date.prototype.toJSON得知

      const obj = {
          date: new Date(),
      };
      
      JSON.stringify(obj); // {"date":"2021-10-08T11:43:31.881Z"}
      

      特性四

      表现不一的undefined、function和symbol

      作为对象键值对时:

      作为值:

      const obj = {
          undefined: undefined,
          fn() {},
          symbol: Symbol()
      };
      
      JSON.stringify(obj); // {}
      

      作为键:

      const fn = function () {};
      const obj = {
          [undefined]: undefined,
          [fn]: function () {},
          [Symbol()]: Symbol()
      };
      
      JSON.stringify(obj); // {}
      

      undefined、function和symbol作为对象的key和value时,会在序列化时将其忽略

      ⚠️:此时可能会改变对象原有的顺序,因为上述三种数据会在序列化时被忽略

      作为数组值时:

      const arr = [undefined, function fn() {}, Symbol()];
      
      JSON.stringify(arr); // [null,null,null]
      

      undefined、function和symbol作为数组的value时,会在序列化时将其都转换为null

      单独存在时:

      JSON.stringify(undefined); // undefined
      JSON.stringify(function () {}); // undefined
      JSON.stringify(Symbol()); // undefined
      

      undefined、function和symbol单独存在时,会在序列化时都转换为undefined

      特性五

      序列化过程中,仅会序列化可枚举属性,不可枚举属性将会忽视

      const obj = {
          name: "nordon",
          age: 18,
      };
      
      // 将age修改为不可枚举属性
      Object.defineProperty(obj, "age", {
          enumerable: false,
      });
      
      JSON.stringify(obj); // {"name":"nordon"}
      

      ⚠️:此举也会改变对象的原有顺序

      特性六

      循环引用的对象,会在序列化时抛出异常

      const obj = {
          name: "nordon",
          age: 18,
      };
      
      const p = {
          name: 'wy',
          obj
      }
      
      obj.p = p
      
      JSON.stringify(obj);
      

      此时会导致控制台抛出异常:

      Uncaught TypeError: Converting circular structure to JSON     --> starting at object with constructor 'Object'     |     property 'p' -> object with constructor 'Object'     --- property 'obj' closes the circle     at JSON.stringify (<anonymous>)

      手动实现stringify

      明白了JSON.stringify的一些特性,接下来便可以依据这些特性动手实现一个kack版本

      在动手实现之前,先利用柯里化封装一些数据类型校验的工具函数:

      const currying = (fn, ...outParams) => {
          // 获取 fn 函数需要的参数个数
          const paramsLen = fn.length;
      
          return (...args) => {
              // 收集全部参数
              let params = [...outParams, ...args];
              // 若参数没有达到 fn 需要的参数,继续收集参数
              if (params.length < paramsLen) {
                  return currying(fn, ...params);
              }
      
              return fn(...params);
          };
      };
      
      /**
       * type: 类型 - [object Array]、[object Number]等
       * source: 数据源
       */
      const judgeType = (type, source) => {
          return Object.prototype.toString.call(source) === type;
      };
      
      const isUndefined = currying(judgeType, "[object Undefined]");
      const isSymbol = currying(judgeTypebgp服务器http://www.558idc.com/yz.html, "[object Symbol]");
      const isFunction = currying(judgeType, "[object Function]");
      const isObject = currying(judgeType, "[object Object]");
      const isNull = currying(judgeType, "[object Null]");
      

      下面直接上代码:

      function jsonStringify(data) {
          let type = typeof data;
      
          if (isNull(data)) { 
      // null 直接返回 字符串'null'
              return "null";
          } else if (data.toJSON && typeof data.toJSON === "function") {
      // 配置了 toJSON函数, 直接使用 toJSON 返回的数据且忽略其他数据
              return jsonStringify(data.toJSON());
          } else if (Array.isArray(data)) {
              let result = [];
              //如果是数组,那么数组里面的每一项类型又有可能是多样的
              data.forEach((item, index) => {
                  if (isUndefined(item) || isSymbol(item) || isFunction(item)) {
                      result[index] = "null";
                  } else {
                      result[index] = jsonStringify(item);
                  }
              });
      
              result = "[" + result + "]";
      
              return result.replace(/'/g, '"');
          } else if (isObject(data)) {
              // 处理普通对象
              let result = [];
              Object.keys(data).forEach((item, index) => {
                  if (typeof item !== "symbol") {
                      //key 如果是 symbol 对象,忽略
                      if (
                          data[item] !== undefined &&
                          typeof data[item] !== "function" &&
                          typeof data[item] !== "symbol"
                      ) {
                          //键值如果是 undefined、function、symbol 为属性值,忽略
                          result.push(
                              '"' + item + '"' + ":" + jsonStringify(data[item])
                          );
                      }
                  }
              });
      
              return ("{" + result + "}").replace(/'/g, '"');
          } else if (type !== "object") {
              let result = data;
      
              //data 可能是基础数据类型的情况在这里处理
              if (Number.isNaN(data) || data === Infinity) {
                  //NaN 和 Infinity 序列化返回 "null"
                  result = "null";
              } else if (isUndefined(data) || isSymbol(data) || isFunction(data)) {
                  // 由于 function 序列化返回 undefined,因此和 undefined、symbol 一起处理
                  return undefined;
              } else if (type === "string") {
                  result = '"' + data + '"';
              }
      
              return String(result);
          }
      }
      

      至此简易版本的JSON.stringify完成,虽然能力尚欠缺许多,主要是提供一个思路,核心注释已注释在代码中,可结合代码和上文的特性一起理解

      总结

      到此这篇关于JSON.stringify实现及其六大特性详解的文章就介绍到这了,更多相关简易版本JSON.stringify及特性内容请搜索hwidc以前的文章或继续浏览下面的相关文章希望大家以后多多支持hwidc!