「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。
哈喽,我是刘十一,今天我们通过代码示例一起总结下JSON.stringify()的特性吧~~~
1、undefined、任意的函数 以及 Symbol 作为 对象的值 时,JSON.stringity() 会跳过(忽略)它们进行序列化。
a: "aaa",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
}
};
JSON.stringify(data); // "{"a":"aaa"}"
2、undefined、任意的函数 以及 Symbol 作为 数组元素 时,JSON.stringify() 会将它们序列化为 null。
JSON.stringify(
["aaa",
undefined,
function aa() {
return true
},
Symbol('dd')
]) ; // "["aaa",null,null,null]"
3、undefined、任意的函数 以及 Symbol 被 JSON.stringify() 作为 单独的值 进行序列化时,都会返回 undefined。
JSON.stringify(function a (){console.log('a')}) // undefined
JSON.stringify(undefined) // undefined
JSON.stringify(Symbol('dd')) // undefined
4、非数组对象 的属性 不能保证 以特定的顺序 出现在序列化后的字符串中。
正如我们在第一特性所说,
JSON.stringify() 序列化时会忽略一些特殊的值,所以不能保证序列化后的字符串还是以特定的顺序出现(数组除外)。
const data = {
a: "aaa",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
},
d: "ddd"
};
JSON.stringify(data); // "{"a":"aaa","d":"ddd"}"
JSON.stringify(["aaa", undefined, function aa() {
return true
}, Symbol('dd'),"eee"]) // "["aaa",null,null,null,"eee"]"
5、转换值 如果有 toJSON() 函数,该函数返回什么值,序列化结果就是什么值,并且忽略其他属性的值。
JSON.stringify({
say: "hello JSON.stringify",
toJSON: function() {
return "today i learn";
}
}) // "today i learn"
6、JSON.stringify() 将会正常序列化 Date 的值。
实际上
Date 对象自己部署了
toJSON() 方法(同Date.toISOString()),因此
Date 对象会被当做字符串处理
JSON.stringify({ now: new Date() });
// "{"now":"2022-02-19T13:58:39.616Z"}"
7、NaN 和 Infinity 格式的数值及 null 都会被当做 null。
JSON.stringify(NaN) // "null"
JSON.stringify(null) // "null"
JSON.stringify(Infinity) // "null"
8、布尔值、数字、字符串等基本类型的包装对象在序列化过程中会自动转换成对应的原始值。
关于基本类型的序列化
JSON.stringify(
[new Number(1), new String("false"), new Boolean(false)]
); // "[1,"false",false]"
9、其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性,不可枚举的属性默认会被忽略。
关于对象属性的是否可枚举
JSON.stringify(
Object.create(
null,
{
x: { value: 'json', enumerable: false },
y: { value: 'stringify', enumerable: true }
}
)
);
// "{"y","stringify"}"
10、对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
我们都知道实现深拷贝最简单粗暴的方式就是序列化:
JSON.parse(JSON.stringify()),这个方式实现深拷贝会因为序列化的诸多特性从而导致诸多的坑点:比如现在我们要说的循环引用问题。
const obj = { name: "loopObj" };
const loopObj = { obj };
// 对象之间形成循环引用,形成闭环
obj.loopObj = loopObj;
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
deepClone(obj)
/**
Uncaught TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'loopObj' -> object with constructor 'Object'
--- property 'obj' closes the circle
at JSON.stringify (<anonymous>)
at deepClone (<anonymous>:9:26)
at <anonymous>:11:13
*/
11、以 symbol 为属性键的属性都会被完全忽略掉,即便
replacer 参数中强制指定包含了它们。
关于 symbol 属性
JSON.stringify(
{ [Symbol.for("json")]: "stringify" },
function(k, v) {
if (typeof k === "symbol") {
return v;
}
}); // undefined
replacer 是 JSON.stringify() 的第二个参数
- replacer 参数有两种形式,可以是一个函数或者一个数组。
- 作为函数时,它有两个参数,键(key)和值(value),函数类似就是数组方法 map、filter 等方法的回调函数,对每一个属性值都会执行一次该函数。
- 如果 replacer 是一个数组,数组的值代表将被序列化成 JSON 字符串的属性名。
replacer 作为函数时,我们可以打破九大特性的大多数特性。
const data = {
a: "aaa",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
}
};
// 不用 replacer 参数时
JSON.stringify(data);
// "{"a":"aaa"}"
// 使用 replacer 参数作为函数时
JSON.stringify(data, (key, value) => {
switch (true) {
case typeof value === "undefined":
return "undefined";
case typeof value === "symbol":
return value.toString();
case typeof value === "function":
return value.toString();
default:
break;
}
return value;
})
// "{"a":"aaa","b":"undefined","c":"Symbol(dd)","fn":"function() {\n return true;\n }"}"
replacer 被传入的函数时,第一个参数不是对象的第一个键值对,而是空字符串作为 key 值,value 值是整个对象的键值对。
const data = {
a: 2,
b: 3,
c: 4,
d: 5
};
JSON.stringify(data, (key, value) => {
console.log(value);
return value;
})
// 第一个被传入 replacer 函数的是 {"":{a: 2, b: 3, c: 4, d: 5}}
// {a: 2, b: 3, c: 4, d: 5}
// 2
// 3
// 4
// 5
replacer 作为数组时,结果非常简单,数组的值就代表了将被序列化成 JSON 字符串的属性名。
const jsonObj = {
name: "JSON.stringify",
params: "obj,replacer,space"
};
// 只保留 params 属性的值
JSON.stringify(jsonObj, ["params"]);
// "{"params":"obj,replacer,space"}"
第三个参数 space。
space 参数用来控制结果字符串里面的间距