快来看看JSON.stringify的实现方式吧

123 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

我们在日常开发中,JSON对象可谓是经常使用,比如使用ajax时需要对请求或返回的数据格式进行转换,比如在对一些简单数据类型的对象做深拷贝时……

众所周知,JSON对象一共有两个方法:JSON.stringify()和JSON.parse()。它们的功能正好相反,前者是将对象转为JSON格式的字符串,而后者是将JSON格式的字符串转为对象。那么JSON.stringify()是如何实现的呢?

JSON.stringify的参数

它有3个参数:

  • 第一个参数是要转换的数据
  • 第二个参数是可选的,它可以是函数,也可以是数组;
    • 如果是函数则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
    • 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;
    • 如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。
  • 第三个参数也是可选的,它是为了美化输出的,如果参数是数字,它代表前面有多少个空格

案列:

let data = [{name:'ly',age:18,sex:'man'},{name:'zs',age:20,sex:'man'},{name:'lili',age:22,sex:'woman'},{name:'lx',age:30,sex:'man'}]
let res = JSON.stringify(data,(key,value)=>{
      if(key==='age') {
          return value*2;
      }
      return value;
})
console.log(res);

image.png

我们发现age都变成两倍了,如果是数组的话:

let data = [{name:'ly',age:18,sex:'man'},{name:'zs',age:20,sex:'man'},{name:'lili',age:22,sex:'woman'},{name:'lx',age:30,sex:'man'}]
let res = JSON.stringify(data,['name'])
console.log(res);

image.png

那么他就只序列化name属性了。

第三个参数的作用就是为了美化代码:我们之前的例子发现输出都不太好看,那么第三个参数就起作用了.我们只需要在第三个参数传入一个数字,假如我们传入4:

let data = [{name:'ly',age:18,sex:'man'},{name:'zs',age:20,sex:'man'},{name:'lili',age:22,sex:'woman'},{name:'lx',age:30,sex:'man'}]
let res = JSON.stringify(data,(key,value)=>{
      if(key==='age') {
          return value*2;
      }
      return value;
},4)
console.log(res);

image.png

这里使用了 4 个空格做为层级缩进。

搞清楚转换规则

做任何事情都要遵守一定的规则,而JSON.stringify的规则就是:

  • 基本数据类型

    1. undefined和symbol类型输出:undefined
    2. number类型输出:字符串类型的number
    3. NaN和Infinity输出:"null"
    4. null输出:"null"
    5. string类型输出:string类型
    6. boolean类型输出:字符串类型的true/false
  • 引用数据类型:

    1. RegExp类型:输出"{}"
    2. Date类型:Date的toString字符串值
    3. 数组中出现了undefined、function和symbol类型:输出null
    4. 普通对象类型:
      • 如果有toJSON方法,那么序列化toJSON()的返回值
      • 如果出现了任意函数、undefined和symbol值,则忽略
      • 所有以symbol为属性键的属性都为被完全忽略

image.png

image.png

所以如果是复杂类型的数据,使用JSON.stringify进行深拷贝是不可取的。

手写实现

现在我们熟悉了JSON.stringify的用法和规则之后,我们就可以自己手动去实现实现这个方法了:

function jsonStringify(data) {
    let type = typeof data;
    if(type !== 'object' && type !== 'function') {
        let result;
        if(type === 'symbol' || type === 'undefined') {
            result = "undefined";
        } else if(Number.isNaN(data) || data === Infinity) {
            result = "null"
        } else {
            result = String(data);
        }
        return result;
    } else if(type ==='function') {
        return "undefined";
    } else {
        if(data === null) {
            return "null"
        } else if(data.toJSON&&typeof data.toJSON === 'function') {
            return jsonStringify(data.toJSON())
        } else if(data instanceof RegExp) {
            return "{}"
        } else if(data instanceof Date) {
            return data.toJSON()
        } else if(Array.isArray(data)) {
            let result = [];
            data.forEach((item,index)=>{
                result[index] = jsonStringify(item);
            })
            return result = "["+result+"]";
        } else {
            let result = [];
            Object.keys(data).forEach((key,index)=>{
                if(typeof key !== 'symbol') {
                    if(data[key] !== 'undefined' && data[key] !== 'symbol' && data[key] !== 'function') {
                        result.push(""+key+""+":"+jsonStringify(data[key]));
                    }
                }
            })
            return ("{"+result+"}").replace(/'/g,'"')
        }
    }
}
let data = [{name:'ly',age:18,sex:'man'},{name:'zs',age:20,sex:'man'},{name:'lili',age:22,sex:'woman'},{name:'lx',age:30,sex:'man'}]
let res = jsonStringify(data)
console.log(res);

image.png

我们发现现在基本已经可以了,唯一的缺点就是key和value为啥没有双引号,等我后面再看看看,或者有没有大佬来解答解答......