【记一道面试题】手写JSON.stringfiy

480 阅读5分钟

前言

平时工作中真的忽略了对这些经典方法的学习,继续加油吧,工作和生活本身也是一个持续学习的过程(ps:一定要走出舒适圈,多出去面试,会发现,自己比想象中的自己更菜)。

先复习一下基本的东西

参考: MDN 但是我更喜欢看高程,可能是讲的比较通俗易懂吧。

可以序列化的类型

  • 简单类型:字符串、数值、布尔值和null。undefined会被忽略NaN和Infinity格式的数值类型会被序列化为null.
  • 复杂类型:对象和数组。其中属性的值既可以是简单类型也可以是复杂类型。 看看MDN上面的列子,比较全
JSON.stringify({});                        // '{}'
JSON.stringify(true);                      // 'true'
JSON.stringify("foo");                     // '"foo"'
JSON.stringify([1, "false", false]);       // '[1,"false",false]'
JSON.stringify({ x: 5 });                  // '{"x":5}'
JSON.stringify(NaN);  //null
JSON.stringify(Infinity);  //null
JSON.stringify(function(){});  //undefined
JSON.stringify(undefined);  //undefined

JSON.stringify({x: 5, y: 6});
// "{"x":5,"y":6}"

JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// '[1,"false",false]'

JSON.stringify({x: undefined, y: Object, z: Symbol("")});
// '{}' 值为undefined、Object、Symbol的属性,在转化的时候会被忽略

JSON.stringify([undefined, Object, Symbol("")]);
// '[null,null,null]'

JSON.stringify({[Symbol("foo")]: "foo"});
// '{}' 属性值为Symbol也会被忽略

JSON.stringify({[Symbol.for("foo")]: "foo"}, [Symbol.for("foo")]);
// '{}'

JSON.stringify(
    {[Symbol.for("foo")]: "foo"},
    function (k, v) {
        if (typeof k === "symbol"){
            return "a symbol";
        }
    }
);


// undefined

// 不可枚举的属性默认会被忽略:
JSON.stringify(
    Object.create(
        null,
        {
            x: { value: 'x', enumerable: false },
            y: { value: 'y', enumerable: true }
        }
    )
);

// "{"y":"y"}"

参数

  • 序列化的对象
  • 过滤器(数组或函数)
  • 用于缩进结果的JSON字符串中的选项
//第二个参数是数组的情况
   let book = {
        title:'Professional Javascript',
        authors:[
            "Nicholas c. Zakes",
            "Matt Frisbie"
        ],
        edition:4,
        year:2017
    }
let jsonText = JSON.stringify(book,['title','edition']);
console.log(jsonText)//{"title":"Professional Javascript","edition":4}
//第二个参数是函数的情况
  let book = {
            title: 'Professional Javascript',
            authors: [
                "Nicholas c. Zakes",
                "Matt Frisbie"
            ],
            edition: 4,
            year: 2017
        }
        let jsonText = JSON.stringify(book, function (key, value) {
            switch (key) {
                case 'authors':
                    return value.join(",");
                case "year":
                    return 5000;
                case 'edition':
                    return undefined;
                default:
                    return value;
            }
        })
  console.log(jsonText)//{"title":"Professional Javascript","authors":"Nicholas c. Zakes,Matt Frisbie","year":5000}

toJSON方法 原生Date上面就有一个toJSON方法,能够自动将JS的Date对象转化为ISO 8601日期字符串

 let book = {
            title:'professional javascript',
            authors:[
                "nicholas c zakes",
                "natt frisbie"
            ],
            edition:4,
            year:2017,
            toJSON:function(){
                return this.title
            }
        }

        let jsonText = JSON.stringify(book);
        console.log(jsonText)//"professional javascript"

可以开始实现了

此版本没有考虑后面几个参数的情况。

第一版:先考虑基本类型

根据上面的分析基本类型中NaN、Infinity undefined symbol 存在特殊情况,需要特殊处理。

 function imitateJSONstringfiy(val) {
            let dataType = typeof val;
            let result = '';
            if (dataType != 'object') {
                //特殊判断number中的NaN和Infinity
                if (Number.isNaN(val) || val === Infinity || val === -Infinity) {
                    result = 'null'
                } else if (dataType == 'undefined' || dataType == 'Function' || dataType == 'Symbol') { //特殊判断undefined  function symbol类型
                    result = 'undefined'
                } else if (dataType == 'string') {//字符串类型会用引号引起来
                    result = '"' + val + '"'
                } else {
                    result = String(val)
                }
                return result

            } else {

            }
        }

第二版考虑复杂类型

考虑null直接返回null

考虑数组

分析:

  1. 数组里面可能还存在其他类型
  2. 数组还存在嵌套
 function imitateJSONstringfiy(val) {
            let dataType = typeof val;
            let result = '';
            if (dataType != 'object') {
                //特殊判断number中的NaN和Infinity
                if (Number.isNaN(val) || val === Infinity || val === -Infinity) {
                    result = 'null'
                } else if (dataType == 'undefined' || dataType == 'unction' || dataType == 'symbol') { //特殊判断undefined  function symbol类型
                    result = 'undefined'
                } else if (dataType == 'string') {//字符串类型会用引号引起来
                    result = '"' + val + '"'
                } else {
                    result = String(val)
                }
                return result

            } else {

                if (val == null) {
                    return 'null'
                } else if (Object.prototype.toString.call(val) == '[object Array]') {//判断是否是数组类型
                    let result = []
                    val.forEach((item, index) => {
                        result[index] = imitateJSONstringfiy(item)
                    })
                    result = "'[" + result.join(',') + "]'"
                    console.log(result)
                }

            }
        }
        imitateJSONstringfiy([undefined, undefined])
        imitateJSONstringfiy([undefined, NaN])

考虑对象类型

  1. 要考虑键值特殊的情况 undefined NaN Infinity之类的键和值都会被忽略
  function imitateJSONstringfiy(val) {
            let dataType = typeof val;
            let result = '';
            if (dataType != 'object') {
                //特殊判断number中的NaN和Infinity
                if (Number.isNaN(val) || val === Infinity || val === -Infinity) {
                    result = 'null'
                } else if (dataType == 'undefined' || dataType == 'function' || dataType == 'symbol') { //特殊判断undefined  function symbol类型
                    result = 'undefined'
                } else if (dataType == 'string') {//字符串类型会用引号引起来
                    result = '"' + val + '"'
                } else {
                    result = String(val)
                }
                return result

            } else {

                if (val == null) {
                    return 'null'
                } else if (Object.prototype.toString.call(val) == '[object Array]') {//判断是否是数组类型
                    let result = []
                    val.forEach((item, index) => {
                        result[index] = imitateJSONstringfiy(item)
                    })
                    result = "'[" + result + "]'"
                    return (result).replace(/'/g, '"');
                } else { //对象
                    let result = [];
                    Object.keys(val).forEach((item, index) => {
                        if (typeof item != 'symbol' && val[item] !== undefined && typeof val[item] !== 'function'
                            && typeof val[item] !== 'symbol') {//键或者值为这几种类型的话会被忽略
                            result.push('"' + item + '"' + ":" + imitateJSONstringfiy(val[item]));
                        }
                    })
                    return ("{" + result + "}").replace(/'/g, '"');
                }
            }
        }
        // imitateJSONstringfiy([undefined, undefined])
        // imitateJSONstringfiy([1, 2, [1, 2]])
        imitateJSONstringfiy({a:1,b:2})

用MDN上面的例子来试试

 console.log(imitateJSONstringfiy({}))// true
        console.log(imitateJSONstringfiy(true))// true
        console.log(imitateJSONstringfiy("foo"))//"foo"
        console.log(imitateJSONstringfiy([1, "false", false]))//"[1,"false",false]"
        console.log(imitateJSONstringfiy({ x: 5 }))// {"x":5}
        console.log(imitateJSONstringfiy(NaN))//null
        console.log(imitateJSONstringfiy(Infinity))//null
        console.log(imitateJSONstringfiy(function () { }))//undefined
        console.log(imitateJSONstringfiy(undefined))//undefined
        console.log(imitateJSONstringfiy({ x: 5, y: 6 }))//{"x":5,"y":6}
        console.log(imitateJSONstringfiy([new Number(1), new String("false"), new Boolean(false)]))//"[{},{"0":"f","1":"a","2":"l","3":"s","4":"e"},{}]"
        console.log(imitateJSONstringfiy({ x: undefined, y: Object, z: Symbol("") }))//{}
        console.log(imitateJSONstringfiy([undefined, Object, Symbol("")]))//"[undefined,undefined,undefined]"
        console.log(imitateJSONstringfiy({ [Symbol("foo")]: "foo" }))//{}
        // console.log(imitateJSONstringfiy({[Symbol.for("foo")]: "foo"}))
        console.log(imitateJSONstringfiy({ [Symbol.for("foo")]: "foo" },//{}
            function (k, v) {
                if (typeof k === "symbol") {
                    return "a symbol";
                }
            }))

这个返回的结果和预期的不同

 console.log(imitateJSONstringfiy([new Number(1), new String("false"), new Boolean(false)]))//"[{},{"0":"f","1":"a","2":"l","3":"s","4":"e"},{}]"

最终代码

这里特殊处理了一下这三种类型

   function imitateJSONstringfiy(val) {
            let dataType = typeof val;
            let result = '';
            if (dataType != 'object') {
                //特殊判断number中的NaN和Infinity
                if (Number.isNaN(val) || val === Infinity || val === -Infinity) {
                    result = 'null'
                } else if (dataType == 'undefined' || dataType == 'function' || dataType == 'symbol') { //特殊判断undefined  function symbol类型
                    result = 'undefined'
                } else if (dataType == 'string') {//字符串类型会用引号引起来
                    result = '"' + val + '"'
                } else {
                    result = String(val)
                }
                return result

            } else {
                debugger
                if (val == null) {
                    return 'null'
                } else if (Object.prototype.toString.call(val) == '[object Array]') {//判断是否是数组类型
                    let result = []
                    val.forEach((item, index) => {
                        result[index] = imitateJSONstringfiy(item)
                    })
                    result = "'[" + result + "]'"
                    return (result).replace(/'/g, '"');
                } else { //对象
                    let result = [];
                    if (val instanceof Number) {
                        val = Number(val)
                        result.push(val)
                    } else if (val instanceof String) {
                        val = '"' + String(val) + '"'
                        result.push(val)
                    } else if (val instanceof Boolean) {
                        val = Boolean(val)
                        result.push(val)
                    } else {
                        Object.keys(val).forEach((item, index) => {
                            if (typeof item != 'symbol' && val[item] !== undefined && typeof val[item] !== 'function'
                                && typeof val[item] !== 'symbol') {//键或者值为这几种类型的话会被忽略
                                result.push('"' + item + '"' + ":" + imitateJSONstringfiy(val[item]));
                            }
                        })
                        return ("{" + result + "}").replace(/'/g, '"');
                    }
                    return result;

                }

            }
        }

参考

死磕 36 个 JS 手写题(搞懂后,提升真的大)