解析JavaScript中JSON.stringify函数
前言
今天我们一起来实现一下JSON.stringify()
函数,我们一步一步来分析和书写代码。
分析和书写
首先来看一个流程
这张图的是指对传入stringify函数的参数进行类型检查,第一步是检查参数是否为null,第二步检查参数类型并且对非object
类型进行处理,第三步则是对object
类型再进行细分。
所以有如下代码:
JSON.myStringify = (jsonObj) => {
// step1 检查参数是否为null
if (jsonObj === null) return 'null';
// step2 检查参数类型并且对非`object`类型进行处理
switch (typeof jsonObj) {
case 'number':
case 'boolean':
return String(jsonObj);
case 'string':
return `"${jsonObj}"`;
case 'undefined':
case 'function':
return 'undefined';
}
// step3 对`object`类型再进行细分
siwtch (Object.prototype.toString.call(jsonObj)) {
case '[object Array]':
case '[object Date]':
case '[object RegExp]':
case '[object Object]':
case '[object Number]':
case '[object Boolean]':
case '[object String]':
}
};
第一步和第二步大家应该很清楚,但是对第三步中Object.prototype.toString.call(jsonObj)
会感到疑惑,其实就是jsonObj
直接调用Object
的原型方法toString
,返回和类型有关的字符串。
这里不明白call
函数的用法可以看我的另一个博客
为什么不直接jsonObj.toString()
呢?因为不同类型可能会对toString
方法进行了重写。
好了我们继续第三步的编写:
Array
类型处理:
...
let strResult = '';
let tempCur = null;
switch (Object.prototype.toString.call(jsonObj)) {
case '[object Array]':
strResult += '[';
/* 迭代 + 递归调用 */
jsonObj.forEach((item) => {
tempCur = JSON.myStringify(item);
// 如果是 undefined 返回 'null'
strResult += `${tempCur === undefined ? 'null' : tempCur},`;
});
/* 将形如 '[1,2,3,4,' 变成 '[1,2,3,4' */
if (strResult !== '[') {
strResult = strResult.slice(0, -1);
}
strResult += ']';
return strResult;
...
}
...
上面我们把strResult
和tempCur
提取到switch
外面是因为在后面的case
语句中还会使用到,而且由于整个switch
中只有一个作用域,无法在不同的case
中声明同一个变量。
Date
类型处理:
let strResult = '';
let tempCur = null;
switch (Object.prototype.toString.call(jsonObj)) {
...
case '[object Date]':
return jsonObj.toJSON === undefined ? jsonObj.toString() : jsonObj.toJSON();
...
}
...
这里首先检查对象有没有toJSON
方法,然后进行处理。
其实toJSON
来源于Date.prototype.toJSON
,是Date
类型的原型对象的方法,那么为什么还要检查呢?是对toJSON
是否被重写为undefined
进行检查。
上面是toStirng
和toJSON
的区别。
RegExp
类型处理:
let strResult = '';
let tempCur = null;
switch (Object.prototype.toString.call(jsonObj)) {
...
case '[object RegExp]':
return '{}';
...
}
...
对于正则的处理较为简单,返回一个'{}'
。
Object
类型处理:
let strResult = '';
let tempCur = null;
switch (Object.prototype.toString.call(jsonObj)) {
...
case '[object Object]':
strResult += '{';
/* 迭代 + 递归调用 */
for (let key in jsonObj) {
if (Object.hasOwnProperty(key)) {
tempCur = JSON.myStringify(jsonObj[key]);
strResult += `${key}:${tempCur},`;
}
}
/* 将形如 '{id:22, age:20,' 变成 '{id:22, age:20' */
if (strResult !== '{') {
strResult = strResult.slice(0, -1);
}
strResult += '}';
return strResult;
...
}
...
这里其实和对Array
类型处理的过程很类似,区别就是递归调用的处理方式不一样,在for ... in
语句中返回jsonObj
对象以及原型链上的属性。
而Object.hasOwnProperty()
仅检查属性是否在jsonObj
上,因此最后就是将jsonObj
上的对象属性进行了遍历。
Number Boolean String
类型处理:
let strResult = '';
let tempCur = null;
switch (Object.prototype.toString.call(jsonObj)) {
...
case '[object Number]':
case '[object Boolean]':
return jsonObj.toString();
case '[object String]':
return `"${jsonObj}"`;
}
...
这三个类型的处理较为简单,和第二步的检查处理很相似,以上就是整个分析流程了。
但是其实JSON.stringify()
函数还有两个可选参数replace
和space
的,为了更加清晰地看到处理的过程,就没有对其进行实现,MDN文档解释如下:
以上就是JSON.stringify()
函数的大概用法,总体的思路就是先类型检查再对应地处理。
后言
另一个与之对应的函数是JSON.parse()
,传入字符串,返回值。
JSON.parse = (jsonStr) => {
eval(`(${jsonStr})`);
};
最后贴一下上面分析的完整代码:
JSON.myStringify = (jsonObj) => {
// step1 检查参数是否为null
if (jsonObj === null) return "null";
// step2 检查参数类型并且对非`object`类型进行处理
switch (typeof jsonObj) {
case "number":
case "boolean":
return String(jsonObj);
case "string":
return `"${jsonObj}"`;
case "undefined":
case "function":
return "undefined";
}
// step3 对`object`类型再进行细分
let strResult = "";
let tempCur = null;
switch (Object.prototype.toString.call(jsonObj)) {
case "[object Array]":
strResult += "[";
/* 迭代 + 递归调用 */
jsonObj.forEach((item) => {
tempCur = JSON.myStringify(item);
// 如果是 undefined 返回 'null'
strResult += `${tempCur === undefined ? "null" : tempCur},`;
});
/* 将形如 '[1,2,3,4,' 变成 '[1,2,3,4' */
if (strResult !== "[") {
strResult = strResult.slice(0, -1);
}
strResult += "]";
return strResult;
case "[object Date]":
return jsonObj.toJSON === undefined
? jsonObj.toString()
: jsonObj.toJSON();
case "[object RegExp]":
return "{}";
case "[object Object]":
strResult += "{";
/* 迭代 + 递归调用 */
for (let key in jsonObj) {
if (Object.hasOwnProperty(key)) {
tempCur = JSON.myStringify(jsonObj[key]);
strResult += `${key}:${tempCur},`;
}
}
/* 将形如 '{id:22, age:20,' 变成 '{id:22, age:20' */
if (strResult !== "{") {
strResult = strResult.slice(0, -1);
}
strResult += "}";
return strResult;
case "[object Number]":
case "[object Boolean]":
return jsonObj.toString();
case "[object String]":
return `"${jsonObj}"`;
}
};