手写实现jsonStringify

453 阅读3分钟

这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战

代码逻辑实现

我们先利用 typeof 把基础数据类型和引用数据类型分开,分开之后再根据不同情况来分别处理不同的情况,按照这个逻辑代码实现如下。

function jsonStringify(data) {
  let type = typeof data;
  if(type !== 'object') {
    let result = data;
    //data 可能是基础数据类型的情况在这里处理

    if (Number.isNaN(data) || data === Infinity) {

       //NaN 和 Infinity 序列化返回 "null"
       result = "null";
    } else if (type === 'function' || type === 'undefined' || type === 'symbol') {

      // 由于 function 序列化返回 undefined,因此和 undefined、symbol 一起处理

       return undefined;

    } else if (type === 'string') {

       result = '"' + data + '"';

    }

    return String(result);

  } else if (type === 'object') {

     if (data === null) {

        return "null"  // 第01讲有讲过 typeof null 为'object'的特殊情况

     } else if (data.toJSON && typeof data.toJSON === 'function') {

        return jsonStringify(data.toJSON());

     } else if (data instanceof Array) {

        let result = [];

        //如果是数组,那么数组里面的每一项类型又有可能是多样的

        data.forEach((item, index) => {

        if (typeof item === 'undefined' || typeof item === 'function' || typeof item === 'symbol') {

               result[index] = "null";

           } else {

               result[index] = jsonStringify(item);

           }

         });

         result = "[" + result + "]";

         return result.replace(/'/g, '"');

      } else {

         // 处理普通对象

         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, '"');

        }

    }

}

手工实现一个 JSON.stringify 方法的基本代码如上面所示,有几个问题你还是需要注意一下:

  • 由于 function 返回 'null', 并且 typeof function 能直接返回精确的判断,故在整体逻辑处理基础数据类型的时候,会随着 undefined,symbol 直接处理了;

  • 关于引用数据类型中的数组,由于数组的每一项的数据类型又有很多的可能性,故在处理数组过程中又将 undefined,symbol,function 作为数组其中一项的情况做了特殊处理;

  • 同样在最后处理普通对象的时候,key (键值)也存在和数组一样的问题,故又需要再针对上面这几种情况(undefined,symbol,function)做特殊处理;

最后在处理普通对象过程中,对于循环引用的问题暂未做检测,如果是有循环引用的情况,需要抛出 Error;

根据官方给出的 JSON.stringify 的第二个以及第三个参数的实现,

整体来说这段代码还是比较复杂的,如果在面试过程中让你当场手写,其实整体还是需要考虑很多东西的。当然上面的代码根据每个人的思路不同,你也可以写出自己认为更优的代码,比如你也可以尝试直接使用 switch 语句,来分别针对特殊情况进行处理,整体写出来可能看起来会比上面的写法更清晰一些,这些可以根据自己情况而定。

实现效果测试

let nl = null;

console.log(jsonStringify(nl) === JSON.stringify(nl));

// true

let und = undefined;

console.log(jsonStringify(undefined) === JSON.stringify(undefined));

// true

let boo = false;

console.log(jsonStringify(boo) === JSON.stringify(boo));

// true

let nan = NaN;

console.log(jsonStringify(nan) === JSON.stringify(nan));

// true

let inf = Infinity;

console.log(jsonStringify(Infinity) === JSON.stringify(Infinity));

// true

let str = "jack";

console.log(jsonStringify(str) === JSON.stringify(str));

// true

let reg = new RegExp("\w");

console.log(jsonStringify(reg) === JSON.stringify(reg));

// true

let date = new Date();

console.log(jsonStringify(date) === JSON.stringify(date));

// true

let sym = Symbol(1);

console.log(jsonStringify(sym) === JSON.stringify(sym));

// true

let array = [1,2,3];

console.log(jsonStringify(array) === JSON.stringify(array));

// true

let obj = {

    name: 'jack',

    age: 18,

    attr: ['coding', 123],

    date: new Date(),

    uni: Symbol(2),

    sayHi: function() {

        console.log("hi")

    },

    info: {

        sister: 'lily',

        age: 16,

        intro: {

            money: undefined,

            job: null

        }

    }

}

console.log(jsonStringify(obj) === JSON.stringify(obj));

// true

上面的这个方法已经实现了,那么用起来会不会有问题呢?我们就用上面的代码,来进行一些用例的检测吧。

上面自己实现的这个 jsonStringify 方法和真正的 JSON.stringify 想要得到的效果是否一样呢?请看下面的测试结果。

通过上面这些测试的例子可以发现,我们自己实现的 jsonStringify 方法基本和 JSON.stringify 转换之后的结果是一样的,不难看出 jsonStringify 基本满足了预期结果。