谨慎使用JSON.stringify
为了避免因为对象是引用类型而造成的数据源污染,我们通常使用 JSON.stringify
将其转换为字符串,而后通过JSON.parse
方法将字符串转化一个新对象来实现深拷贝。但是在这个过程中也会存在一些问题,本文就介绍一下使用JSON.stringify
可能遇到的一些问题,尽可能在以后避免这些问题。
先看一段代码:
let obj = {
name: 'leo',
age: Infinity
}
let originObj = JSON.stringify(obj)
console.log(originObj) // {"name":"leo","age":null}
可以看到在转换过程中Infinity
变成了null
。
先看看解决办法:
- 简单粗暴,重新赋值
age
属性 - 使用
JSON.stringify
的第二个参数
function censor(key, value) {
if (value === Infinity) {
return "Infinity";
}
return value;
}
const b = JSON.stringify(obj, censor);
const c = JSON.parse(
b,
function (key, value) {
return value === "Infinity" ? Infinity : value;
}
);
console.log(c); // {name: 'leo', age: Infinity}
作为参考,大家可能直接用了第一种方法。但是这里可以看到
JSON.stringify
其实还有第二个参数,那么它有什么用呢?接下来我们就来揭开它的神秘面纱。
JSON.stringify 的基本语法
语法:
JSON.stringify(value[, replacer [, space]])
JSON.stringify()
方法将JavaScript
对象或值转换为JSON
字符串,如果指定了替换函数,则可选择替换值;如果指定了替换数组,则可选择仅包含指定的属性。
简单来说,JSON.stringify()
将一个值转换为相应的 JSON
格式的字符串。
参数替换器
也就是第二个参数(replacer
),该参数是可选的,可以是函数或数组。
当是函数时,在序列化过程中,每个待序列化的属性都会被函数进行转换处理。
let replacerFun = function (key, value) {
console.log(key, value)
if (key === 'name') {
return undefined
}
return value
}
let myIntro = {
name: 'leo',
age: 25,
like: 'FE'
}
console.log(JSON.stringify(myIntro, replacerFun))
// {"age":25,"like":"FE"}
这实际上是一个过滤函数,它利用了JSON.stringify
中的特性,如果对象属性值未定义,则在序列化时会被忽略(稍后我们会提到)。
在开始时,replacer
函数将传递一个空字符串作为键值,代表要字符串化的对象。
上面console.log(key, value)
输出的值如下:
{name: 'leo', age: 25, like: 'FE'} // 其实是 '' {name: 'leo', age: 25, like: 'FE'}, 不过''是个空字符
ame leo
age 25
like FE
{"age":25,"like":"FE"}
可见,通过第二个参数,我们可以更加灵活地操作和修改序列化目标的值。
当第二个参数是数组时,只会序列化数组中包含的属性名称:
JSON.stringify (myIntro, [ 'name' ]) // {"name":"leo"}
第三个参数
指定一个空字符串进行缩进,更常见的是指定一个数字,代表几个空格。
console.log(JSON.stringify(myIntro))
console.log(JSON.stringify(myIntro, null, 2))
// 输出
// {"name":"leo","age":25,"like":"FE"}
// {
// "name": "leo",
// "age": 25,
// "like": "FE"
// }
JSON.stringify使用场景
判断对象/数组值是否相等
let a = [1,2,3],
b = [1,2,3];
JSON.stringify(a) === JSON.stringify(b);// true
localStorage/sessionStorage 存储对象
我们知道localStorage/sessionStorage
只能存储字符串。当我们要存储对象时,需要使用 JSON.stringify
将其转换为字符串,然后在获取对象时使用 JSON.parse
解析出来。
function setLocalStorage(key,val) {
window.localStorage.setItem(key, JSON.stringify(val));
};
function getLocalStorage(key) {
let val = JSON.parse(window.localStorage.getItem(key));
return val;
};
实现对象深拷贝
let myIntro = {
name: 'leo',
age: 25,
like: 'FE'
}
function deepClone() {
return JSON.parse(JSON.stringify(myIntro))
}
let copyMe = deepClone(myIntro)
copyMe.like = 'js only'
console.log(myIntro, copyMe)
// { name: 'leo', age: 25, like: 'FE' } { name: 'leo', age: 25, like: 'js only' }
路由(浏览器地址)参数传递
由于浏览器参数只能通过字符串传递,所以还需要JSON.stringify
。
使用 JSON.stringify 的注意事项
在某些场景下使用 JSON.stringify
可能会引发一些难以发现的问题:
转换属性值中有toJSON方法
如果转换值中有toJSON
方法,则该方法返回的值将是最终的序列化结果。
let toJsonMyIntro = {
name: "Gopal",
age: 25,
like: "FE",
toJSON: function () {
return "frontend";
},
};
console.log(JSON.stringify(toJsonMyIntro)); // "frontend"
转换后的值中有未定义、任意函数、符号值
分为两种情况:
一种是数组对象,未定义的、任意函数和符号值都会被转换为null
。
JSON.stringify([undefined, Object, Symbol("")]);
// '[null,null,null]'
一种是非数组对象,序列化时会被忽略。
JSON.stringify({ x: undefined, y: Object, z: Symbol("") });
// '{}'
对于这些情况,我们可以使用JSON.stringify
的第二个参数来使其满足我们的期望。
const testObj = { x: undefined, y: Object, z: Symbol("test") }
const resut = JSON.stringify(testObj, function (key, value) {
if (value === undefined) {
return 'undefined'
} else if (typeof value === "symbol" || typeof value === "function") {
return value.toString()
}
return value
})
console.log(resut)
// {"x":"undefined","y":"function Object() { [native code] }","z":"Symbol(test)"}
含有循环引用的对象
let objA = {
name: "leo",
}
let objB = {
age: 25,
}
objA.age = objB
objB.name = objA
JSON.stringify(objA)
上面的代码会报错: VM1140:11 Uncaught TypeError: Converting circular structure to JSON
以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
值为NaN和Infinity
数组值或具有 NaN
和 Infinity
值的非数组对象属性将转换为 null
let me = {
name: "leo",
age: Infinity,
money: NaN,
};
let originObj = JSON.stringify(me);
console.log(originObj); // {"name":"leo","age":null,"money":null}
JSON.stringify([NaN, Infinity])
// [null,null]
具有不可枚举的属性值
默认情况下,不可枚举属性被忽略。
let person = Object.create(null, {
name: { value: "leo", enumerable: false },
age: { value: "25", enumerable: true },
})
console.log(JSON.stringify(person))
// {"age":"25"}