开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情
-
iterableList用于存放可以继续遍历的数据类型;specialList用于存放比较特殊的Undefined、Symbol_basic、Function三种类型,特殊在于:对象 key 的 value 如果是这些类型,则序列化的时候会丢失,数组的元素如果是这些类型,则序列化的时候会统一转化为"null"。因为这三种类型要多次用到,所以先存起来。 -
为什么要将最终返回的
res初始化为一个空数组?因为:- 如果我们处理的
target是数组,则只需要调用map就可以将数组的每一个元素映射为序列化之后的结果,调用后返回的数组赋给res,再和[、]字符拼接,会隐式调用数组的toString方法,产生一个标准的序列化结果; - 如果处理的
target是对象字面量,则可以将它的每个 key-value 的序列化结果 push 到res中,最终再和{、}字符拼接,也同样会产生一个标准的序列化结果。 - 在整个过程中不需要去处理 JSON 字符串中的逗号分隔符。
- 如果我们处理的
-
对于对象字面量,类型为
"Symbol_basic"的属性会丢失,属性值为Undefined、Symbol_basic、Function三种类型的属性也会丢失。属性丢失其实就是在遍历对象的时候略过这些属性 -
在检测循环引用的时候,存在嵌套关系的对象应该共享同一条父级链,所以递归的时候需要把存放父级链的数组传进去;同时,不存在嵌套关系的两个对象不应该共享同一条父级链(否则会将所有互相引用的情况都误认为是循环引用),所以每次遍历对象 key 的时候,都会重新生成一个
currentArray。 -
最后,为保险起见,记得将序列化结果中可能出现的所有单引号替换为双引号
最终代码和效果
最终代码如下:
function getType(o) {
return typeof o === "symbol"
? "Symbol_basic"
: Object.prototype.toString.call(o).slice(8, -1);
}
function isObject(o) {
return o !== null && (typeof o === "object" || typeof o === "function");
}
function processOtherTypes(target, type) {
switch (type) {
case "String":
return `"${target.valueOf()}"`;
case "Number":
case "Boolean":
return target.valueOf().toString();
case "Symbol":
case "Error":
case "RegExp":
return "{}";
case "Date":
return `"${target.toJSON()}"`;
case "Function":
return undefined;
default:
return null;
}
}
function checkCircular(obj, currentParent) {
let type = getType(obj);
if (type == "Object" || type == "Array") {
if (currentParent.includes(obj)) {
throw new TypeError("Converting circular structure to JSON");
}
currentParent.push(obj);
}
}
function jsonStringify(target, initParent = [target]) {
let type = getType(target);
let iterableList = ["Object", "Array", "Arguments", "Set", "Map"];
let specialList = ["Undefined", "Symbol_basic", "Function"];
if (!isObject(target)) {
if (type === "Symbol_basic" || type === "Undefined") {
return undefined;
} else if (Number.isNaN(target) || target === Infinity || target === -Infinity) {
return "null";
} else if (type === "String") {
return `"${target}"`;
}
return String(target);
}
else {
let res;
if (!iterableList.includes(type)) {
res = processOtherTypes(target, type);
} else {
if (type === "Array") {
res = target.map((item) => {
if (specialList.includes(getType(item))) {
return "null";
} else {
let currentParent = [...initParent];
checkCircular(item, currentParent);
return jsonStringify(item, currentParent);
}
});
res = `[${res}]`.replace(/'/g, '"');
} else {
res = [];
Object.keys(target).forEach((key) => {
if (getType(key) !== "Symbol_basic") {
let type = getType(target[key]);
if (!specialList.includes(type)) {
let currentParent = [...initParent];
checkCircular(target[key], currentParent);
res.push(`"${key}":${jsonStringify(target[key], currentParent)}`);
}
}
});
res = `{${res}}`.replace(/'/g, '"');
}
}
return res;
}
}
拿下面的 obj 对象测试一下效果:
let obj = {
tag: Symbol("student"),
money: undefined,
girlfriend: null,
fn: function(){},
info1: [1,'str',NaN,Infinity,-Infinity,undefined,null,() => {},Symbol()],
info2: [new Set(),new Map(),new Error(),/a+b/],
info2: {
name: 'Chor',
age: 20,
male: true
},
info3: {
date: new Date(),
tag: Symbol(),
fn: function(){},
un: undefined
},
info4:{
str: new String('abc'),
no: new Number(123),
bool: new Boolean(false),
tag: Object(Symbol())
}
}
结果如下:
最后,并没有实现 JSON.stringify() 中的 replacer 参数和 space 参数。