前言
我的项目中遇到一个 bug,一个组件为了保留一份 JSON 对象,应用 JSON.stringify 将其转换成字符串,这样做当然是为了防止对象是援用类型造成数据源的净化。
但发现前面应用 JSON.parse 办法之后,发现数据有所变动。
代码简化:
let obj = {
name: 'Gopal',
age: Infinity
}
let originObj = JSON.stringify(obj)
console.log(originObj) // {"name":"Gopal","age":null}
能够看到,Infinity 变成了 null,从而导致了前面的 bug。其实我的项目中本人踩 JSON.stringify 的坑曾经很多了,借此机会好好整顿一下,也给大家一个参考。
先说下这个问题的解决办法:
解决办法 1:
简略粗犷,从新给 age 属性赋值
解决办法 2:
function censor(key, value) {if (value === Infinity) {return "Infinity";}
return value;
}
var b = JSON.stringify(a, censor);
var c = JSON.parse(
b,
function (key, value) {return value === "Infinity" ? Infinity : value;}
);
这就有点绕了,当做参考吧,其实我本人是间接应用了第一种办法。不过这里能够看到 JSON.stringify 实际上还有第二个参数,那它有什么用呢?接下来咱们揭开它的神秘面纱。
JSON.stringify 根底语法
JSON.stringify(value[, replacer [, space]])
概念
MDN 中文文档对它的解释如下:
JSON.stringify() 办法将一个 JavaScript 值 (对象或者数组) 转换为一个 JSON 字符串,如果指定了 replacer 是一个函数,则能够选择性地替换值,或者如果指定了 replacer 是一个数组,则可选择性地仅蕴含数组指定的属性。
我集体感觉这样解释是有所不妥的,不妥之处在于“对象或者数组”,因为实际上对于一般的值,咱们也能够应用 JSON.stringify,只是咱们很少这么用罢了。不过这个问题不大,咱们文中介绍的也都是针对对象或者数组。
JSON.stringify('foo'); // '"foo"'
而英文版 MDN 的解释就会正当很多:
The JSON.stringify() method converts a JavaScript object or value to a JSON string, optionally replacing values if a replacer function is specified or optionally including only the specified properties if a replacer array is specified.
简略来说,JSON.stringify() 就是将值转换为相应的 JSON 格局字符串。
JSON.stringify 弱小的第二个参数 replacer
这个参数是可选的,能够是一个函数,也能够是一个数组
当是一个函数的时候,则在序列化的过程中,被序列化的每个属性都会通过该函数的转换和解决,看如下代码:
let replacerFun = function (key, value) {console.log(key, value)
if (key === 'name') {return undefined}
return value
}
let myIntro = {
name: 'Gopal',
age: 25,
like: 'FE'
}
console.log(JSON.stringify(myIntro, replacerFun))
// {"age":25,"like":"FE"}
这里其实就是一个筛选的作用,利用的是 JSON.stringify 中对象属性值为 undefined 就会在序列化中被疏忽的个性(前面咱们会提到)。
值得注意的是,在一开始 replacer 函数会被传入一个空字符串作为 key 值,代表着要被 stringify 的这个对象。
下面 console.log(key, value) 输入的值如下:
{name: 'Gopal', age: 25, like: 'FE'}
name Gopal
age 25
like FE
{"age":25,"like":"FE"}
能够看出,通过第二个参数,咱们能够更加灵便的去操作和批改被序列化指标的值。
当第二个参数为数组的时候,只有蕴含在这个数组中的属性名才会被序列化:
JSON.stringify(myIntro, ['name']) // {"name":"Gopal"}
中看不中用的第三个参数
指定缩进用的空白字符串,更多时候就是指定一个数字,代表几个空格:
let myIntro = {
name: 'Gopal',
age: 25,
like: 'FE'
}
console.log(JSON.stringify(myIntro))
console.log(JSON.stringify(myIntro, null, 2))
// {"name":"Gopal","age":25,"like":"FE"}
// {
// "name": "Gopal",
// "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: 'Gopal',
age: 25,
like: 'FE'
}
function deepClone() {return JSON.parse(JSON.stringify(myIntro))
}
let copyMe = deepClone(myIntro)
copyMe.like = 'Fitness'
console.log(myIntro, copyMe)
// {name: 'Gopal', age: 25, like: 'FE'} {name: 'Gopal', age: 25, like: 'Fitness'}
路由(浏览器地址)传参
因为浏览器传参只能通过字符串进行,所以也是须要用到 JSON.stringify。
JSON.stringify 应用注意事项
看了下面,是不是感觉 JSON.stringify 性能很弱小,立马想在我的项目中尝试了呢?稍等稍等,先看完以下的注意事项再做决定吧,可能在某些场景下会触发一些难以发现的问题。
转换属性值中有 toJSON 办法,慎用
转换值中如果有 toJSON 办法,该办法返回的值将会是最初的序列化后果。
// toJSON
let toJsonMyIntro = {
name: "Gopal",
age: 25,
like: "FE",
toJSON: function () {return "前端杂货铺";},
};
console.log(JSON.stringify(toJsonMyIntro)); // "前端杂货铺"
被转换值中有 undefined、任意的函数以及 symbol 值,慎用
分为两种状况:
一种是数组对象,undefined、任意的函数以及 symbol 值会被转换成 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: "Gopal",}
let objB = {age: 25,}
objA.age = objB
objB.name = objA
JSON.stringify(objA)
会报以下谬误:
Uncaught TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'age' -> object with constructor 'Object'
--- property 'name' closes the circle
at JSON.stringify (<anonymous>)
at <anonymous>:1:6
以 symbol 为属性键的属性,慎用
所有以 symbol 为属性键的属性都会被齐全疏忽掉,即使 replacer 参数中强制指定蕴含了它们。
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: "Gopal",
age: Infinity,
money: NaN,
};
let originObj = JSON.stringify(me);
console.log(originObj); // {"name":"Gopal","age":null,"money":null}
JSON.stringify([NaN, Infinity])
// [null,null]
具备不可枚举的属性值时,慎用
不可枚举的属性默认会被疏忽:
let person = Object.create(null, {name: { value: "Gopal", enumerable: false},
age: {value: "25", enumerable: true},
})
console.log(JSON.stringify(person))
// {"age":"25"}
总结
JSON.stringify 在理论利用中的确很不便的解决了咱们很多问题,比方简略的深拷贝等。
然而咱们在应用时候,也须要晓得它有哪些不足之处,在目标值可能是一些非凡值的状况下,可能序列化后的后果会不合乎咱们的预期,这个时候就须要慎用。