解析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;
	...
}
...

上面我们把strResulttempCur提取到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进行检查。
在这里插入图片描述
上面是toStirngtoJSON的区别。

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()函数还有两个可选参数replacespace的,为了更加清晰地看到处理的过程,就没有对其进行实现,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}"`;
  }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值