• 研究一下 JSON.parse(JSON.stringify(obj))


    start

    • 工作中,经常需要深拷贝一份数据,用来做逻辑处理。
    • 在 JS 中 最最常见的就是 JSON.parse(JSON.stringify(obj))
    • 相信绝大多数人都知道,可以使用这个方式去深拷贝,但是它有一些局限性的。
    • 但是具体的局限性在哪里?母鸡呀。
    • 这就是我写这篇博客的意义。

    JSON.stringify()

    先了解一下基本的用法

    1. 语法

    JSON.stringify(value[, replacer [, space]])
    
    • 1

    2. 参数

    value
    将要序列化成 一个 JSON 字符串的值。

    replacer 可选

    1. 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
    2. 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;
    3. 如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。

    space 可选

    1. 指定缩进用的空白字符串,用于美化输出(pretty-print);
    2. 如果参数是个数字,它代表有多少的空格;上限为 10。
    3. 该值若小于 1,则意味着没有空格;
    4. 如果该参数为字符串(当字符串长度超过 10 个字母,取其前 10 个字母),该字符串将被作为空格;
    5. 如果该参数没有提供(或者为 null),将没有空格。

    3. 返回值

    一个表示给定值的 JSON 字符串。
    
    • 1

    4. 异常

    • 当在循环引用时会抛出异常 TypeError ("cyclic object value")(循环对象值)
    • 当尝试去转换 BigInt 类型的值会抛出 TypeError ("BigInt value can't be serialized in JSON")(BigInt 值不能 JSON 序列化).

    5. 个人总结

    1. value可以转换对象,也可以转换 简单类型的值;
    2. replacer 有三种情况,函数,数组,空;
    3. space 如果为字符串,则会直接替换掉空格;

    个人总结练习

    // 1.1.1 转换对象
    console.log(
      JSON.stringify({ name: 'tomato' }),
      typeof JSON.stringify({ name: 'tomato' })
    ) // '{"name":"tomato"}' string
    
    // 1.1.2 转换数组
    console.log(
      JSON.stringify([1, 2, 3]),
      typeof JSON.stringify({ name: 'tomato' })
    ) // '[1,2,3]' string
    // 1.1.1 function 返回的是undefined
    
    // 1.1.3 转换函数
    console.log(
      JSON.stringify(function () {
        console.log(123)
      }),
      typeof JSON.stringify(function () {
        console.log(123)
      })
    ) // undefined  undefined
    
    // 1.2 转换简单类型
    console.log(JSON.stringify(1), typeof JSON.stringify(1)) // '1' string
    console.log(JSON.stringify('1'), typeof JSON.stringify('1')) // "1" string
    console.log(JSON.stringify(false), typeof JSON.stringify(false)) // 'false' string
    console.log(JSON.stringify(null), typeof JSON.stringify(null)) // 'null' string
    // 1.2.1  undefined 和 Symbol有点特殊,返回的是 undefined
    console.log(JSON.stringify(undefined), typeof JSON.stringify(undefined)) // undefined undefined
    console.log(JSON.stringify(Symbol('a')), typeof JSON.stringify(Symbol('a'))) // undefined undefined
    // 1.2.2 BigInt 类型的会报异常
    // console.log(JSON.stringify(10n)) // TypeError: Do not know how to serialize a BigInt
    
    // 2. replacer 的三种情况:
    // 2.1 replacer 是函数的情况
    console.log(
      JSON.stringify({ name: 'tomato', sex: 'boy', age: 18 }, (key, value) => {
        return typeof value === 'number' ? undefined : value
      })
    )
    // '{"name":"tomato","sex":"boy"}'
    
    // 2.2  replacer 是数组的情况
    console.log(JSON.stringify({ name: 'tomato', sex: 'boy', age: 18 }, ['name']))
    // '{"name":"tomato"}'
    
    // 2.2 null 或者 未提供
    console.log(JSON.stringify({ name: 'tomato', sex: 'boy', age: 18 }))
    // '{"name":"tomato","sex":"boy","age":18}'
    
    // 3. 指定space(美化输出)
    console.log(JSON.stringify({ x: 5, y: '你好' }, null, 1))
    /*
    {
      "x": 5,
      "y": "你好"
    }
    */
    
    console.log(JSON.stringify({ x: 5, y: '你好' }, null, 100))
    /*
    {
              "x": 5,
              "y": "你好"
    }
    */
    
    console.log(JSON.stringify({ x: 5, y: '你好' }, null, 'tomato12345678'))
    /*
    {
    tomato1234"x": 5,
    tomato1234"y": "你好"
    }
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    6. 九大特性

    1. undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined

    2. 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化。

    3. 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。

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

    5. 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。

    6. 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。

    7. Date 日期调用了 toJSON() 将其转换为了 string 字符串(同 Date.toISOString()),因此会被当做字符串处理。

    8. NaN 和 Infinity 格式的数值及 null 都会被当做 null。

    9. 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。

    依次解读:

    虽然看着文字有点多,但是不是很难,九大特性都要记住,现在我们依次去实验一下。

    特性一:

    特性一我们又可以对它进行拆分,如下:

    • 在非数组对象的属性值中时 undefined、任意的函数以及 symbol 值,会被忽略
    • 在数组中时 undefined、任意的函数以及 symbol 值, 转换成 null(出现在数组中时)。
    • 函数、undefined 被单独转换时,会返回 undefined
    // 一.非数组对象
    let obj = {
      name: 'tomato',
      // 空字符串不会被忽略
      other: '',
      // 函数 会被忽略
      say: function() {
        console.log('say')
      },
      // Symbol 会被忽略
      sym: Symbol('123'),
      // undefined 会被忽略
      un: undefined
    }
    
    // {"name":"tomato","other":""}
    
    
    
    // 二.数组
    let a = [
      'tomato',
      '',
      function() {
        console.log('say')
      },
      Symbol('123'),
      undefined
    ]
    // ["tomato","",null,null,null]
    
    
    // 三.单独转换 返回undefined
    console.log(
      JSON.stringify(function() {
        console.log(123)
      }),
      typeof JSON.stringify({ name: 'tomato' })
    ) // undefined  string
    
    console.log(JSON.stringify(undefined), typeof JSON.stringify(undefined)) // undefined undefined
    console.log(JSON.stringify(Symbol('a')), typeof JSON.stringify(Symbol('a'))) // undefined undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    特性二: 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化。

    如果转换的值存在 toJSON() 方法,那么序列化的是这个函数的返回值。

    function tomato() {
      console.log('你好')
    }
    
    // 1. 单独转换函数==> undefined
    console.log(JSON.stringify(tomato)) // undefined
    
    // 2.存在 toJSON属性为一个返回值为 '起飞' 的函数
    tomato.toJSON = function() {
      return '起飞'
    }
    console.log(JSON.stringify(tomato)) // '起飞'
    
    // 3.存在 toJSON属性为一个返回值为 函数 的函数
    tomato.toJSON = function() {
      return function() {
        return '第二个函数'
      }
    }
    console.log(JSON.stringify(tomato)) // undefined
    
    // 4.存在 toJSON属性为一个返回值为 字符串
    tomato.toJSON = '我是番茄,如果toJSON不是方法'
    console.log(JSON.stringify(tomato)) // undefined
    
    // 5.原型上有 toJSON方法
    function T2() {
      console.log('新建一个函数')
    }
    T2.prototype.toJSON = function() {
      return '原型上的toJSON方法'
    }
    let t2 = new T2()
    console.log(JSON.stringify(t2)) // 原型上的toJSON方法
    
    // 个人总结: 如果转换值自身或者原型上存在toJSON()方法; 最终结果为 将toJSON()的返回值-序列化后的结果
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    特性三: 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中

    这个怎么解释呢。因为在 非数组对象中 undefined, function , Symbol,会被忽略,所以顺序不能保证

    let obj = {
      name: '番茄',
      say() {
        console.log('‘你好呀')
      },
      other: '理论上我是第三个属性'
    }
    
    console.log(JSON.stringify(obj))
    // {"name":"番茄","other":"理论上我是第三个属性"}
    
    let arr = [
      '番茄',
      function() {
        console.log('‘你好呀')
      },
      '理论上我是第三个属性'
    ]
    console.log(JSON.stringify(arr))
    // ["番茄",null,"理论上我是第三个属性"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    console.log(typeof JSON.stringify(new String('123')), JSON.stringify(new String('123'))) // string "123"
    
    console.log(typeof JSON.stringify(new Number(123)), JSON.stringify(new Number(123))) // string "123"
    
    console.log(typeof JSON.stringify(new Boolean(false)), JSON.stringify(new Boolean(false))) // string "false"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    特性五: 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误; 当尝试去转换 BigInt 类型的值会抛出 TypeError

    let obj = {
      name: '番茄'
    }
    
    obj.like = obj
    
    console.log(obj) // { name: '番茄', like: [Circular] }
    
    console.log(JSON.stringify(obj)) // TypeError: Converting circular structure to JSON
    
    console.log(JSON.stringify(10n)) // TypeError: Do not know how to serialize a BigInt
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    特性六: 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。

    let sy = Symbol('key')
    
    let obj = {
      // 特性一 Symbol值会被忽略
      name: Symbol('to'),
      // 特性六
      [sy]: '123'
    }
    
    console.log(JSON.stringify(obj)) // {}
    
    console.log(
      JSON.stringify(obj, (key, value) => {
        if (typeof key === 'string') {
          return '我是字符串' + key
        }
    
        if (typeof key === 'symbol') {
          return '我是symbol' + key
        }
      })
    ) // 我是字符串
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    特性七: Date 日期调用了 toJSON() 将其转换为了 string 字符串(同 Date.toISOString()),因此会被当做字符串处理。

    let obj = {
      date: new Date(),
      date2: new Date().getTime(),
      date3: '123'
    }
    
    console.log(JSON.stringify(obj, null, 4))
    /*
    
    {
        "date": "2022-07-19T09:15:07.969Z",
        "date2": 1658222107969,
        "date3": "123"
    }
    
    */
    
    /*
    !! 这里啰嗦一句,之前使用axios,调用接口
    
    传参就是:
    let params = {
      time : new Date()
    }
    
    实际发送给后端的数据格式 :  "2022-07-19T09:15:07.969Z"
    当时是百思不得其解,现在就一目了然了,大概率axios源码中,使用了JSON.stringify()序列化了参数
    */
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    特性八: NaN 和 Infinity 格式的数值及 null 都会被当做 null。

    console.log(JSON.stringify({
      n1: NaN,
      n2: Infinity,
      n3: null
    }))
    // '{"n1":null,"n2":null,"n3":null}'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    特性九 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。

    let enumerableObj = {}
    
    Object.defineProperties(enumerableObj, {
      name: {
        value: '番茄',
        enumerable: true
      },
      sex: {
        value: 'boy',
        enumerable: false
      }
    })
    
    console.log(JSON.stringify(enumerableObj))
    // '{"name":"番茄"}'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    7. 手写一个简易的 JSON.stringify

    这里借鉴的别人手写的代码。写的很好就直接

    放一下原文链接:原文链接

    // 写一个简易版本的 JSON.stringify  ==> myStringify方法  (忽略后面两个参数了)
    function myStringify(data) {
      // 定义判断是否是循环引用的方法
      const isCyclic = (obj) => {
        let stackSet = new Set()
        let detected = false
    
        // 检测 对象 是否循环引用
        const detect = (obj) => {
          // 不是对象直接 return
          if (obj && typeof obj !== 'object') {
            return
          }
          // 当要检查的对象已经存在于stackSet中时,表示存在循环引用
          if (stackSet.has(obj)) {
            return (detected = true)
          }
          // 将当前obj存储到stackSet
          stackSet.add(obj)
    
          for (let key in obj) {
            // 对obj下的属性进行递归检测
            if (obj.hasOwnProperty(key)) {
              detect(obj[key])
            }
          }
          // 平级检测完成之后,将当前对象删除,防止误判
          /*
          例如:对象的属性指向同一引用,如果不删除的话,会被认为是循环引用
          let tempObj = {
            name: '前端胖头鱼'
          }
          let obj4 = {
            obj1: tempObj,
            obj2: tempObj
          }
        */
          stackSet.delete(obj)
        }
    
        detect(obj)
    
        return detected
      }
    
      // 特性五 循环引用
      if (isCyclic(data)) {
        throw new TypeError('Converting circular structure to JSON')
      }
      // 特性五 BigInt
      if (typeof data === 'bigint') {
        throw new TypeError('Do not know how to serialize a BigInt')
      }
    
      const type = typeof data
      const commonKeys1 = ['undefined', 'function', 'symbol']
      const getType = (s) => {
        return Object.prototype.toString
          .call(s)
          .replace(/\[object (.*?)\]/, '$1')
          .toLowerCase()
      }
    
      // 非对象
      if (type !== 'object' || data === null) {
        let result = data
        // 特性八:  NaN 和 Infinity 格式的数值及 null 都会被当做 null。
        if ([NaN, Infinity, null].includes(data)) {
          result = 'null'
          // 特性一 ③:
          // `undefined`、`任意的函数`以及`symbol值`被`单独转换`时,会返回 undefined
        } else if (commonKeys1.includes(type)) {
          // 直接得到undefined,并不是一个字符串'undefined'
          return undefined
        } else if (type === 'string') {
          result = '"' + data + '"'
        }
    
        return String(result)
      } else if (type === 'object') {
        // 特性二:
        // 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化
        // 特性七:
        // Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。
        if (typeof data.toJSON === 'function') {
          return myStringify(data.toJSON())
        } else if (Array.isArray(data)) {
          let result = data.map((it) => {
            // 特性一 ②:
            // `undefined`、`任意的函数`以及`symbol值`出现在`数组` 中时会被转换成  `null`
            return commonKeys1.includes(typeof it) ? 'null' : myStringify(it)
          })
    
          return `[${result}]`.replace(/'/g, '"')
        } else {
          // 特性四:
          // 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
          if (['boolean', 'number'].includes(getType(data))) {
            return String(data)
          } else if (getType(data) === 'string') {
            return '"' + data + '"'
          } else {
            let result = []
            // 特性八
            // 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性
            Object.keys(data).forEach((key) => {
              // 特性三:
              // 所有以symbol为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
              if (typeof key !== 'symbol') {
                const value = data[key]
                // 特性一 ①:
                // `undefined`、`任意的函数`以及`symbol值`,出现在`非数组对象`的属性值中时在序列化过程中会被忽略
                if (!commonKeys1.includes(typeof value)) {
                  result.push(`"${key}":${myStringify(value)}`)
                }
              }
            })
    
            return `{${result}}`.replace(/'/, '"')
          }
        }
      }
    }
    
    myStringify()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126

    JSON.parse

    1.语法

    在这里插入图片描述

    2.注意事项

    JSON.parse(JSON.stringify(obj)) 的时候经常会遇到这种报错;

    Uncaught SyntaxError: Unexpected token u in JSON at position 0
        at JSON.parse ()
        at :1:6
    
    • 1
    • 2
    • 3

    首先考虑 JSON.parse() 参数为undefined或 空字符串的情况:

    在这里插入图片描述

    参考文章

    1. https://juejin.cn/post/7017588385615200270

    end

    • 这几天在研究深拷贝,考虑到JS的新功能也一直在增加,所以会有很多种特殊情况,我这里再补充一些情况。
    let obj = {
      err: new Error('出错啦'),
      regx: new RegExp('aaa')
    }
    
    console.log(JSON.stringify(obj))
    // {"err":{},"regx":{}}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    【NodeJs-5天学习】第三天实战篇① ——10行代码给她造个熬夜提醒睡觉机器人
    【OpenCV】-查找并绘制轮廓
    cmake cpack打包代码
    CS 611 Legends: Monsters and Heroes
    系统调用理论详解,Linux操作系统原理与应用
    [BJDCTF2020]Mark loves cat
    【iOS开发】iOS App的加固保护原理:使用ipaguard混淆加固
    【信号处理】Matlab实现CDR-based噪声和混响抑制
    搭建nginx https 反向代理 http tomcat服务实践。
    基于PLC的机械手控制系统设计
  • 原文地址:https://blog.csdn.net/wswq2505655377/article/details/125879881