对于这两个方法,在以前的工作当作也仅仅只是使用,但是并没有去深入研究,只是简单的知道一个是将对象或者数组转为 JSON
字符串,另外一个就是反过来转换 JSON
的,但是深入了解以后却发现东西还是有点多的,特别是还支持第二个参数配置,于是便查阅资料深入了解一下,在这里记录记录
顺便安利一个扩展知识 JSON 解析器
JSON.parse()
JSON.parse()
用来解析 JSON
字符串,构造由字符串描述的 JavaScript
值或对象,提供可选的 reviver
函数用以在返回之前对所得到的对象执行变换
1 | var json = '{"result": true, "count": 42}' |
语法如下
1 | JSON.parse(text[, reviver]) |
如果被解析的 JSON
字符串是非法的,则会抛出一个语法错误异常(需要注意,JSON.parse()
不允许最后一个键值对后面存在逗号),如果指定了 reviver
函数,则解析出的 JavaScript
值(解析值)会经过一次转换后才将被最终返回(返回值)
意思就是,解析值本身以及它所包含的所有属性,会按照一定的顺序(从最最里层的属性开始,一级级往外,最终到达顶层,也就是解析值本身)分别的去调用 reviver
函数,在调用过程中,当前属性所属的对象会作为 this
值,当前属性名和属性值会分别作为第一个和第二个参数传入 reviver
中,如果 reviver
返回 undefined
,则当前属性会从所属对象中删除,如果返回了其他值,则返回的值会成为当前属性新的属性值
需要注意的是
- 当遍历到最顶层的值(解析值)时,传入
reviver
函数的参数会是空字符串""
(因为此时已经没有真正的属性)和当前的解析值(有可能已经被修改过了) - 当前的
this
值会是{"": 修改过的解析值}
,在编写reviver
函数时,要注意到这个特例(这个函数的遍历顺序依照从最内层开始,按照层级顺序,依次向外遍历)
下面是两个示例
1 | // 如果到了最顶层,则直接返回属性值,否则将属性值变为原来的 2 倍 |
JSON.stringify()
JSON.stringify()
的总体用法可以参考上图,下面我们慢慢来看,该方法可以将一个 JavaScript
值(对象或者数组)转换为一个 JSON
字符串,如果指定了 replacer
是一个函数,则可以替换值,或者如果指定了 replacer
是一个数组,可选的仅包括指定的属性,基本语法如下
1 | JSON.stringify(value[, replacer [, space]]) |
关于可选参数 replacer
- 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理
- 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的
JSON
字符串中 - 如果该参数为
null
或者未提供,则对象所有的属性都会被序列化
关于可选参数 space
- 指定缩进用的空白字符串,用于美化输出(
pretty-print
) - 如果参数是个数字,它代表有多少的空格(上限为
10
),该值若小于1
,则意味着没有空格 - 如果该参数为字符串(字符串的前十个字母),该字符串将被作为空格
- 如果该参数没有提供(或者为
null
)将没有空格
关于序列化,有下面五点注意事项
- 非数组对象的属性『不能保证以特定的顺序』出现在序列化后的字符串中
- 布尔值、数字、字符串的包装对象在序列化过程中会『自动转换成对应的原始值』
undefined
、任意的函数以及Symbol
值,如果出现在非数组对象的属性值中时,在序列化过程中会被忽略,如果出现在数组中时将被转换成null
,此外NaN
和Infinity
格式的数值及null
也都会被当做null
来进行处理- 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误,这也就是为什么去实现深拷贝时,遇到循环引用的对象会抛出错误的原因
- 所有以
Symbol
为属性键的属性都会被完全忽略掉,即便replacer
参数中强制指定包含了它们 - 其他类型的对象,包括
Map/Set/WeakMap/WeakSet
仅会序列化可枚举的属性,也就是说『不可枚举的属性会被忽略』
所以我们在使用该方法进行对象拷贝的时候就需要注意了,下面来看几个示例加深一下印象
1 | JSON.stringify({}) // '{}' |
JSON.stringify() 的 replacer 参数
replacer
参数可以是一个函数或者一个数组,作为函数,它有两个参数,键值都会被序列化
- 如果返回一个数值,转换成相应的字符串被添加入
JSON
字符串 - 如果返回一个字符串,该字符串作为属性值被添加入
JSON
- 如果返回一个布尔值,
"true"
或者"false"
被作为属性值被添加入JSON
字符串 - 如果返回任何其他对象,该对象递归地序列化成
JSON
字符串,对每个属性调用replacer
方法(除非该对象是一个函数,这种情况将不会被序列化成JSON
字符串) - 如果返回
undefined
,该属性值不会在JSON
字符串中输出
需要注意的是,不能用
replacer
方法,从数组中移除值(values
),如若返回undefined
或者一个函数,将会被null
取代
1 | var foo = { foundation: 'Mozilla', model: 'box', week: 45, transport: 'car', month: 7 } |
如果 replacer
是一个数组,数组的值代表将被序列化成 JSON
字符串的属性名(即只有包含在这个数组中的属性名才会被序列化)
1 | var foo = { foundation: 'Mozilla', model: 'box', week: 45, transport: 'car', month: 7 } |
关于 toJSON 方法
需要注意的是,如果一个被序列化的对象拥有 toJSON
方法,那么该 toJSON
方法就会覆盖该对象默认的序列化行为(即为调用 toJSON
方法后的返回值会被序列化)
1 | var obj = { |
此外 JSON.stringify()
还将会正常序列化 Date
对象,如下所示
1 | // {"now": "2018-03-01T08:42:34.893Z"} |
原因是因为 Date
对象内部已经部署了 toJSON()
方法(同 Date.toISOString()
),因此 Date
对象会被当做字符串处理
使用 JSON.stringify 来格式化对象
在平常的开发过程中,经常会遇到一些十分复杂的对象,往往是对象当中嵌套对象,看上去十分的不直观,我们可以利用 replacer
和 space
参数来对其进行格式化
1 | // 因为函数如果出现在非数组对象的属性值中时,在序列化过程中会被忽略,所以需要特殊处理 |