其实有不少有用的东西,当时学习了,也记住了,可是时间久了就是记不住,因此致使在平常开发中老是想不起来原来这个东西能够这么用,而去选择了更加复杂和麻烦的方式。其实咱们平常学习的知识就是拿来用的,即便你今天把知识点背下来了,没有去思考这个知识点咱们能够用来干吗,不须要几天就会慢慢地忘掉。因此今天咱们来了解一下在平常学习时你遗漏掉或者忘掉或者没有思考过的你不知道的 JSON.stringify()
的威力。javascript
原文戳我java
JSON.stringify()
首先咱们在开发的过程中遇到这样一个处理数据的需求git
const todayILearn = {
_id: 1,
content: '今天学习 JSON.stringify(),我很开心!',
created_at: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
updated_at: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)'
}
复制代码
咱们须要将上面这个对象处理成下面这个对象github
const todayILearn = {
id: 1,
content: '今天学习 JSON.stringify(),我很开心!',
createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)'
}
复制代码
也就是在不改变属性的值的前提下,将对象属性修改一下。 把_id
改为 id
,把 updated_at
改为 updatedAt
,把 created_at
改为 createdAt
。咱们如今经过这个小小的需求来见识一下 JSON.stringify()
的强大吧。面试
首先要解决这个问题咱们有不少种解决方式,咱们先提供两种不优雅的解决方案:json
// 多一个变量存储
const todayILearnTemp = {};
for (const [key, value] of Object.entries(todayILearn)) {
if (key === "_id") todayILearnTemp["id"] = value;
else if (key === "created_at") todayILearnTemp["createdAt"] = value;
else if (key === "updatedAt") todayILearnTemp["updatedAt"] = value;
else todayILearnTemp[key] = value;
}
console.log(todayILearnTemp);
// 结果:
// { id: 1,
// content: '今天学习 JSON.stringify(),我很开心!',
// createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
// updated_at: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)'
// }
复制代码
方案一彻底没有问题,能够实现。可是多声明了一个变量又加上一层循环而且还有不少的 if
else
语句,怎么都显得不太优雅。数组
delete
属性和增长属性// 极致的暴力美学
todayILearn.id = todayILearn._id;
todayILearn.createdAt = todayILearn.created_at;
todayILearn.updatedAt = todayILearn.updated_at;
delete todayILearn._id;
delete todayILearn.created_at;
delete todayILearn.updated_at;
console.log(todayILearn);
// 太暴力😢
//{
// content: '今天学习 JSON.stringify(),我很开心!',
// id: 1,
// createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
// updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)'
//}
复制代码
直接 delete 暴力解决太粗鲁了,并且有一个缺点,属性的顺序变了。函数
replace
美学典范const mapObj = {
_id: "id",
created_at: "createdAt",
updated_at: "updatedAt"
};
JSON.parse(
JSON.stringify(todayILearn).replace(
/_id|created_at|updated_at/gi,
matched => mapObj[matched])
)
// {
// id: 1,
// content: '今天学习 JSON.stringify(),我很开心!',
// createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (中国标准时间)',
// updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (中国标准时间)'
// }
复制代码
瞬间感受很是优雅和舒服,有木有!oop
接下来,正片开始,咱们今天将系统的学习或者说是复习一遍 JSON.stringify
的基础知识,让咱们在平常开发中更加的游刃有余。学习
JSON.stringify()
九大特性JSON.stringify()
第一大特性对于 undefined
、任意的函数以及 symbol
三个特殊的值分别做为对象属性的值、数组元素、单独的值时 JSON.stringify()
将返回不一样的结果。
首先,咱们来复习一下知识点,看一道很是简单的面试题目:请问下面代码会输出什么?
const data = {
a: "aaa",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
}
};
JSON.stringify(data); // 输出:?
// "{"a":"aaa"}"
复制代码
很简单这道题目面试官主要考察的知识点是:
undefined
、任意的函数以及 symbol
做为对象属性值时 JSON.stringify()
将跳过(忽略)对它们进行序列化面试官追问:假设 undefined
、任意的函数以及 symbol
值做为数组元素会是怎样呢?
JSON.stringify(["aaa", undefined, function aa() {
return true
}, Symbol('dd')]) // 输出:?
// "["aaa",null,null,null]"
复制代码
知识点是:
undefined
、任意的函数以及 symbol
做为数组元素值时,JSON.stringify()
会将它们序列化为 null
咱们稍微再动下脑筋,若是单独序列化这些值会是什么样的结果呢?
JSON.stringify(function a (){console.log('a')})
// undefined
JSON.stringify(undefined)
// undefined
JSON.stringify(Symbol('dd'))
// undefined
复制代码
单独转换的结果就是:
undefined
、任意的函数以及 symbol
被 JSON.stringify()
做为单独的值进行序列化时都会返回 undefined
JSON.stringify()
第一大特性总结undefined
、任意的函数以及 symbol
做为对象属性值时 JSON.stringify()
对跳过(忽略)它们进行序列化
undefined
、任意的函数以及 symbol
做为数组元素值时,JSON.stringify()
将会将它们序列化为 null
undefined
、任意的函数以及 symbol
被 JSON.stringify()
做为单独的值进行序列化时,都会返回 undefined
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"]"
复制代码
正如咱们在第一特性所说,JSON.stringify()
序列化时会忽略一些特殊的值,因此不能保证序列化后的字符串仍是以特定的顺序出现(数组除外)。
JSON.stringify()
第三大特性toJSON()
函数,该函数返回什么值,序列化结果就是什么值,而且忽略其余属性的值。JSON.stringify({
say: "hello JSON.stringify",
toJSON: function() {
return "today i learn";
}
})
// "today i learn"
复制代码
JSON.stringify()
第四大特性JSON.stringify()
将会正常序列化 Date
的值。JSON.stringify({ now: new Date() });
// "{"now":"2019-12-08T07:42:11.973Z"}"
复制代码
实际上 Date
对象本身部署了 toJSON()
方法(同Date.toISOString()),所以 Date
对象会被当作字符串处理。
JSON.stringify()
第五大特性NaN
和 Infinity
格式的数值及 null
都会被当作 null
。直接上代码:
JSON.stringify(NaN)
// "null"
JSON.stringify(null)
// "null"
JSON.stringify(Infinity)
// "null"
复制代码
JSON.stringify()
第六大特性关于基本类型的序列化:
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// "[1,"false",false]"
复制代码
JSON.stringify()
第七大特性关于对象属性的是否可枚举:
// 不可枚举的属性默认会被忽略:
JSON.stringify(
Object.create(
null,
{
x: { value: 'json', enumerable: false },
y: { value: 'stringify', enumerable: true }
}
)
);
// "{"y":"stringify"}"
复制代码
JSON.stringify()
第八大特性咱们都知道实现深拷贝最简单粗暴的方式就是序列化: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)
/** VM44:9 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 */
复制代码
这也就是为何用序列化去实现深拷贝时,遇到循环引用的对象会抛出错误的缘由。
JSON.stringify()
第九大特性最后,关于 symbol
属性还有一点要说的就是:
symbol
为属性键的属性都会被彻底忽略掉,即使 replacer
参数中强制指定包含了它们。JSON.stringify({ [Symbol.for("json")]: "stringify" }, function(k, v) {
if (typeof k === "symbol") {
return v;
}
})
// undefined
复制代码
replacer
是 JSON.stringify()
的第二个参数,咱们比较少用到,因此不少时候咱们会忘记 JSON.stringify()
第二个、第三个参数,场景很少,可是用的好的话会很是方便,关于 JSON.stringify()
第九大特性的例子中对 replacer
参数不明白的同窗先别急,其实很简单,咱们立刻就会在下面的学习中弄懂。
replacer
replacer
参数有两种形式,能够是一个函数或者一个数组。做为函数时,它有两个参数,键(key)和值(value),函数相似就是数组方法 map
、filter
等方法的回调函数,对每个属性值都会执行一次该函数。若是 replacer
是一个数组,数组的值表明将被序列化成 JSON 字符串的属性名。
replacer
做为函数时第二个参数replacer
很是强大, 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 }"}"
复制代码
虽然使用 toString() 方法有点耍流氓的意思可是不得不说第二个参数很强大。
replacer
函数的第一个参数须要注意的是,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
复制代码
map
函数咱们还能够用它来手写实现一个对象的相似 map 的函数。
// 实现一个 map 函数
const data = {
a: 2,
b: 3,
c: 4,
d: 5
};
const objMap = (obj, fn) => {
if (typeof fn !== "function") {
throw new TypeError(`${fn} is not a function !`);
}
return JSON.parse(JSON.stringify(obj, fn));
};
objMap(data, (key, value) => {
if (value % 2 === 0) {
return value / 2;
}
return value;
});
// {a: 1, b: 3, c: 2, d: 5}
复制代码
replacer
做为数组时replacer
做为数组时,结果很是简单,数组的值就表明了将被序列化成 JSON 字符串的属性名。
const jsonObj = {
name: "JSON.stringify",
params: "obj,replacer,space"
};
// 只保留 params 属性的值
JSON.stringify(jsonObj, ["params"]);
// "{"params":"obj,replacer,space"}"
复制代码
space
space
参数用来控制结果字符串里面的间距。首先看一个例子就是到这东西究竟是干啥用的:
const tiedan = {
name: "弹铁蛋同窗",
describe: "今天在学 JSON.stringify()",
emotion: "like shit"
};
JSON.stringify(tiedan, null, "🐷");
// 接下来是输出结果
// "{
// 🐷"name": "弹铁蛋同窗",
// 🐷"describe": "今天在学 JSON.stringify()",
// 🐷"emotion": "like shit"
// }"
JSON.stringify(tiedan, null, 2);
// "{
// "name": "弹铁蛋同窗",
// "describe": "今天在学 JSON.stringify()",
// "emotion": "like shit"
// }"
复制代码
上面代码一眼就能看出第三个参数的做用了,花里胡哨的,其实这个参数仍是比较鸡肋的,除了好看没啥特别的用处。咱们用 \t
、 \n
等缩进能让输出更加格式化,更适于观看。
若是是一个数字, 则在字符串化时每一级别会比上一级别缩进多这个数字值的空格(最多10个空格);
若是是一个字符串,则每一级别会比上一级别多缩进该字符串(或该字符串的前10个字符)。
JSON.stringify()
九大特性:1、对于 undefined
、任意的函数以及 symbol
三个特殊的值分别做为对象属性的值、数组元素、单独的值时的不一样返回结果。
undefined
、任意的函数以及 symbol
做为对象属性值时 JSON.stringify()
跳过(忽略)对它们进行序列化
undefined
、任意的函数以及 symbol
做为数组元素值时,JSON.stringify()
将会将它们序列化为 null
undefined
、任意的函数以及 symbol
被 JSON.stringify()
做为单独的值进行序列化时都会返回 undefined
2、非数组对象的属性不能保证以特定的顺序出如今序列化后的字符串中。
3、转换值若是有 toJSON()
函数,该函数返回什么值,序列化结果就是什么值,而且忽略其余属性的值。
4、JSON.stringify()
将会正常序列化 Date
的值。
5、NaN
和 Infinity
格式的数值及 null
都会被当作 null
。
6、布尔值、数字、字符串的包装对象在序列化过程当中会自动转换成对应的原始值。
7、其余类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
8、对包含循环引用的对象(对象之间相互引用,造成无限循环)执行此方法,会抛出错误。
9、全部以 symbol
为属性键的属性都会被彻底忽略掉,即使 replacer
参数中强制指定包含了它们。
JSON.stringify()
第二个参数和第三个参数map
、filter
等方法的回调函数,对每个属性值都会执行一次该函数(期间咱们还简单实现过一个 map
函数)。replacer
是一个数组,数组的值表明将被序列化成 JSON 字符串的属性名。若是是一个数字, 则在字符串化时每一级别会比上一级别缩进多这个数字值的空格(最多10个空格);
若是是一个字符串,则每一级别会比上一级别多缩进该字符串(或该字符串的前10个字符)。
第一个例子的方案三,有小伙伴 @Anlimwei 提示说这个方案会有风险,确实是这样的(可能会把对象的值给替换掉)。你们慎用吧,大部分状况下这样使用是 ok 的。