JSON.stringify()实现原理

210 阅读5分钟

转载: blog.csdn.net/gtLBTNq9mr3…

一、原理学习

JSON 的语法可以表示以下三种类型的值。

1、 简单值:使用与 JavaScript  相同的语法,可以在JSON 中表示字符串、数值、布尔值和 null。但JSON 不支持 JavaScript 中的特殊值 undefined。

2、 对象:对象作为一种复杂数据类型,表示的是一组无序的键值对儿。而每个键值对儿中的值可以是简单值,也可以是复杂数据类型的值。

3、 数组:数组也是一种复杂数据类型,表示一组有序的值的列表,可以通过数值索引来访问其中的值。数组的值也可以是任意类型——简单值、对象或数组。

let myObj = {
  undef: undefined,
  bool: false,
  fun: function(){},
  date: new Date(),
  arr: [1, 2],
  obj: {a: 1, b: 2},
  reg: /\d/,
  sym: Symbol(),
  nul: null,
  set: new Set(),
  map: new Map()
}
console.log(JSON.stringify(myObj))
 
 
// {"bool":false,"date":"2020-05-27T03:22:47.587Z","arr":[1,2],"obj":{"a":1,"b":2},"reg":{},"nul":null,"set":{},"map":{}}

这个例子使用 JSON.stringify() 把一个JavaScript 对象序列化为一个JSON 字符串,现在,我们已经了解了 JSON.stringify() 方法的输出以及它的工作方式,让我们从序列化值开始实现它。

序列化值,首先,我们将从以下数据类型开始。

1、undefined

2、number

3、boolean

4、string


function stringify(value) {
  // 参数类型
  var type = typeof value;
 
 
  function getValues(value) {
    if (type === "undefined") {
      return undefined;
    }
 
 
    if (type === "number" || type === "boolean") {
      return "" + value + "";
    }
 
 
    if (type === "string") {
      return '"' + value + '"';
    }
  }
 
 
  return getValues(value);
}
 
 
console.log(stringify(1)); // "1"
console.log(stringify("abc")); // ""abc""
console.log(stringify(true)); // "true"
 
 
// 这里是 undefined 而不是 "undefined"
console.log(stringify(undefined) === JSON.stringify(undefined)); // true

到目前为止,上述功能是比较简单。它所做的只是用引号把值引起来,但是 undefined 并不需要转换为字符串,而是直接返回 undefined 数据类型。

现在,我们将添加对更多数据类型的支持,例如

1、array

2、object

3、null

4、date

5、functions (methods)

为了支持数组和对象,我们应该解析属性之间的多层嵌套。我们必须递归地处理子元素并序列化值。

对于数组而言,它非常简单,请使用一个开括号和一个闭括号对数组进行迭代,然后调用该stringify()函数,然后依次调用该getValues()函数并重复进行,直到所有值都考虑在内。

但是对于对象,我们需要使用对象字面量的左,右括号将值和属性都进行序列化。

对于日期对象,还有一件有趣的事情,JSON.stringify()方法返回的值为 ISO 8601 日期字符串(与在Date对象上调用toISOString()的结果完全一样)。


function stringify(value) {
  var type = typeof value;
 
 
  function getValues(value) {
    if (type === "symbol" || type === "undefined" || type === "function") {
      return undefined;
    }
 
 
    if (type === "number" || type === "boolean") {
      return "" + value + "";
    }
 
 
    if (type === "string") {
      return '"' + value + '"';
    }
  }
 
 
  // 对于对象数据类型
  // 在javascript中,数组和对象都是对象
  if (type === "object") {
 
 
    // 检查值是否为null
    if (!value) {
      return "" + value + "";
    }
 
 
    // 检查值是否为日期对象
    if (value instanceof Date) {
      return '"' + new Date(value).toISOString() + '"'; // 返回ISO 8601日期字符串
    }
 
 
    // 检查值是否为Array
    if (value instanceof Array) {
      return "[" + value.map(stringify) + "]"; // 递归调用stringify函数
 
 
    } else {
      // 递归调用stringify函数
      return (
        "{" +
        Object.keys(value).map(
          key => {
            let result = stringify(value[key])
            if (result === undefined) {
              return undefined
            }
            return '"' + key + '"' + ":" + result
          }
        ).filter(item => item !== undefined) +
        "}"
      );
    }
  }
 
 
  return getValues(value);
}
 
 
console.log(stringify([123])); // "[1,2,3]"
console.log(stringify(new Date())); // 返回日期字符串
console.log(stringify({ a1 })); // "{"a":1}"
// 一、语法

// Object.keys(obj)

// 参数:要返回其枚举自身属性的对象

// 返回值:一个表示给定对象的所有可枚举属性的字符串数组

// 二、处理对象,返回可枚举的属性数组

// let person = {name:"张三",age:25,address:"深圳",getName:function(){}}

// Object.keys(person) // ["name", "age", "address","getName"]



// Array.map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值,同时不会改变原来的数组。
// filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

上面的函数现在适用于所有数据类型,并且输出与 JSON.stringify() 方法相同。

实际上,JSON.stringify() 除了要序列化的 JavaScript 对象外,还可以接收另外两个参数,这两个参数用于指定以不同的方式序列化 JavaScript 对象。第一个参数是个过滤器,可以是一个数组,也可以是一个函数;第二个参数是一个选项,表示是否在 JSON 字符串中保留缩进。单独或组合使用这两个参数,可以更全面深入地控制 JSON 的序列化。(这里实现的代码暂时不支持这两个参数)

与 JavaScript 不同,JSON 中对象的属性名任何时候都必须加双引号。手工编写 JSON 时,忘了给对象属性名加双引号或者把双引号写成单引号都是常见的错误。

如果对象中有 undefined,那么相应的属性会被忽略。

二、循环引用

var obj = {a:1,b:3}
obj.c = obj
JSON.stringify(obj)//报错

定义一个全局cache数组,每当待序列化的JavaScript对象的属性被遍历时,将该属性对应的值存储到cache数组去。

如果遍历时发现,有属性值已经在cache数组里有值了,说明检测到了循环引用,此时直接return退出循环即可。

var cache = [];
var str = JSON.stringify(o, function(key, value) {
    if (typeof value === 'object' && value !== null) {
        if (cache.indexOf(value) !== -1) {
            // 移除
            return;
        }
        // 收集所有的值
        cache.push(value);
    }
    return value;
});
cache = null; // 清空变量,便于垃圾回收机制回收