前言
工作中经常使用 JSON.stringify
方法存储 localStorage
,深拷贝对象,用的最多的就是第一个参数,甚至不知道它还有第二个和第三个参数,所以详细的整理了一下 JSON.stringify
用法和特性,使我们能够真正的能灵活运用这个方法。
语法
JSON.stringify(value[, replacer [, space]])
复制代码
参数
replacer 参数
replacer
参数可以是一个函数或者一个数组。作为函数,它有两个参数,键(key)和值(value),它们都会被序列化。
值得注意的是,在开始时, replacer
函数会被传入一个空字符串作为 key
值,value
代表着要被 stringify
的这个对象。随后每个对象或数组上的属性会被依次传入。
总的来说 replacer
参数就是用来手动忽略一些不想被序列化的属性,有点类似过滤器的作用
var foo = {
id: 1,
name: "sf",
age: 18,
};
//作为函数,函数没有返回值或者返回值为 undefined 时,忽略这个属性值
JSON.stringify(foo, (key, value) => {
if (typeof value === "string") {
return undefined;
}
return value;
});
//{"id":1,"age":18}
//作为数组,数组的值代表将被序列化成 JSON 字符串的属性名
JSON.stringify(foo, ['id',"name"]);
//{"id":1,"name":"sf"}
复制代码
space 参数
space
参数用来控制结果字符串里面的间距。如果是一个数字, 则在字符串化时每一级别会比上一级别缩进多这个数字值的空格(最多10个空格);如果是一个字符串,则每一级别会比上一级别多缩进该字符串(或该字符串的前10个字符)。实际使用基本都是用来美化输出。
let a = JSON.stringify({ a: 1, b: 2 }, null, 2);
let b = JSON.stringify({ a: 1, b: 2 }, null, " ");
console.log(a == b); //true
JSON.stringify({ a: 1, b: 2 }, null, "--");
// {
// --"a": 1,
// --"b": 2
// }
复制代码
特性描述
1. undefined、Symbol值、函数
- 出现在对象属性值中:
undefined
、Symbol值
、函数
,在序列化过程中将会被忽略 - 出现在数组中:
undefined
、Symbol值
、函数
会被转化为 null - 单独转换时: 会返回 undefined
const obj = {
a: "a",
b: undefined,
c: Symbol(),
d: function () {},
};
JSON.stringify(obj)
// {"a":"a"}
const arry = [undefined, Symbol("c"), function () {}];
JSON.stringify(arry);
//[null,null,null]
JSON.stringify(undefined);
// undefined
JSON.stringify(Symbol(111));
// undefined
JSON.stringify(function () {});
// undefined
复制代码
2. 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中
正如在第一特性所说,JSON.stringify()
序列化时会忽略一些特殊的值,所以不能保证序列化后的字符串还是以特定的顺序出现(数组除外)。
3. 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值
JSON.stringify([new Boolean(true), new Number(1), new String("a")]);
// [true,1,"a"]
复制代码
4. 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化
const obj = {
a: "aaa",
toJSON() {
return "hello world";
},
};
JSON.stringify(obj);
// "hello world"
复制代码
5. 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
const obj = {
name: "loopObj",
};
const loopObj = {
obj,
};
// 对象之间形成循环引用,形成闭环
obj.loopObj = loopObj;
JSON.stringify(obj);
//TypeError: Converting circular structure to JSON
复制代码
6. 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer
参数中强制指定包含了它们。. 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
const obj = {
a: "aaa",
[Symbol("foo")]: "foo",
};
JSON.stringify(obj);
// {"a":"aaa"}
JSON.stringify(obj, function (k, v) {
if (typeof k === "symbol") {
return "a symbol";
}
});
// undefined
复制代码
7. 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。
JSON.stringify({
date: new Date("2022-02-02"),
})
// {"date":"2022-02-02T00:00:00.000Z"}
复制代码
8. NaN 和 Infinity 格式的数值及 null 都会被当做 null。
JSON.stringify([NaN, Infinity, 1 / 0, Number("a")]);
// [null,null,null,null]
复制代码
9. 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
// 不可枚举的属性默认会被忽略:
JSON.stringify(
Object.create(null, {
x: { value: "x", enumerable: false },
y: { value: "y", enumerable: true },
})
);
// "{"y":"y"}"
复制代码
应用
localStorage
localStorage
中的键值对总是以字符串的形式存储,所以当我们需要把一个对象存在 localStorage
中时,只能用 JSON.stringify
将其转化成字符串存储,使用的时候用 JSON.parse
方法去取
const userInfo = { user: "admin" };
localStorage.setItem("userInfo", JSON.stringify(userInfo));
JSON.parse(localStorage.getItem("userInfo"));
// {user: 'admin'}
复制代码
对象深拷贝
使用 JSON.parse(JSON.stringify)
是实现对象的深拷贝最简单粗暴的方法。但是由于 JSON.stringify
的一些特性,会产生问题,例如:
undefined
、Symbol
、 函数, 对象中会被忽略,数组中会被序列化成null
。NaN
、Infinity
和-Infinity
会被序列化成null
。- 循环引用问题,
stringify
会报错。
当确定不存在以上情况时,才考虑使用 JSON.parse(JSON.stringify)
进行深拷贝。
属性过滤
当接口返回一大堆数据,我们只想存某几个属性的时候,通过 replacer
函数过滤属性是一个不错的小技巧。
var foo = {
id: 1,
name: "sf",
age: 18,
};
localStorage.setItem("user", JSON.stringify(foo, ["id", "name"]));
localStorage.getItem("user");
//{"id":1,"name":"sf"}
复制代码