JSON.stringify是序列化对象的方法,可以将对象转换为字符串方便传输和储存,除此之外,用它来实现深拷贝也是棒棒的。为了对这个api有更深的理解,今天让我们用手写代码的方式来实现一下吧。
测试用例:
const wife = {
name: 'name',
age: 25,
[Symbol()]: 222,
birth:new Date(),
nick:/wife/,
like:undefined,
cook:function(){console.log('cook')},
husband:null,
list:[{test:'ee'}],
children: [
{
name: 'name',
age: 26,
},
{
name: 'name',
age: 222,
}
],
}
在手动实现json.stringify之前,先让我们来看一眼真正的JSON.stringify(wife)返回的是个什么样的玩意儿:
{
"name": "name",
"age": 25,
"birth": "2021-10-29T06:44:11.758Z",
"nick": {},
"husband": null,
"list": [
{
"test": "ee"
}
],
"children": [
{
"name": "name",
"age": 26
},
{
"name": "name",
"age": 222
}
]
}
根据这个返回的结果,我们注意到了以下几个点
- value为日期对象的返回了一个字符串,但这个字符串好像也不是toString()直接返回的
- value为正则表达式的直接返回了一个空对象
- value为函数或者undefined或者Symbol的直接过滤掉了
- value为null的情况仍然返回为null
- value为数组仍然返回了一个数组,只是把数组中的每一项都序列化了
为什么会出现这种情况呢,这时我们打开了mdn(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)看到了以下说明
转换值如果有 toJSON() 方法,该方法定义什么值将被序列化。
Date 日期调用了 toJSON() 将其转换为了字符串(同Date.toString()),因此会被当做字符串处理。
有了这句话,第一点就清楚了,这个日期返回的字符串当然不是toString()的值,因为我们的日期对象有toJSON()方法,这个字符串是由toJSON()返回的
undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined).
有了这一点,第三点也迎刃而解了
NaN 和 Infinity 格式的数值及 null 都会被当做 null
嗯 解释了第四点
关于第二点和和第五点,mdn上没有明显的说明。但根据笔者理解,RegExp和Date一样作为一个特殊的对象但又没有toJSON方法,作为没有显式属性的对象转换出来自然成了一个{};而数组本来就是特殊的对象,输出{"0":ele1:"1":ele2}的话也太奇怪了。
弄清楚了JSON.stringify的序列化规则以后,让我们动手吧嘻嘻:
function myStringify(obj){
//忽略undefind和function 先置为undefined 组合的时候忽略
if(obj === undefined || typeof(obj) === 'function'){
return undefined;
}
//null或者NaN Infinity置为null
if(obj === null){
return null;
}
// 时间对象转为字符串
if(obj instanceof Date){
return `"${obj.toJSON()}"`
}
//regExp输出{}
if(obj instanceof RegExp){
return `{}`
}
//字符串 置为“spring” 布尔 数字原样输出
if(typeof(obj)!== 'object'){
return typeof(obj) === 'string' ? `"${obj}"`:obj
}
//数组
if(Array.isArray(obj)){
let arrStr = obj.map(item => `${myStringify(item)}` )
return `[${arrStr.join(',')}]`
}
//采用Object.getOwnPropertyNames直接过滤掉symbol
let keyNames = Object.getOwnPropertyNames(obj);
const arrObj = keyNames.map((item) => {
return `${myStringify(obj[item]) !== undefined && `"${item}":${myStringify(obj[item])}`}`
})
return `{${arrObj.join(',')}}`
}
用这个方法序列化测试用例和JSON.stringify()一毛一样
问题:
如果这个方法要加入JSON.stringify的第二个参数,代码应该怎么样设计呢