「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
前言
最近在复习JS相关的知识,大佬们都是手撕这个源码,手撕那个源码的,我连能看下去真的就不错了,所以正好最近复习到深浅拷贝,JSON这部分,想着就跟大家一起来复习一下吧!以前也没想过JSON.stringify是怎么实现的,但是复习的地方有说到,就仔细看了下,也没有很深的研究,就是对一些普通类型进行了判断,就想和大家分享一下!
JS数据类型及其判断复习
基础数据类型及引用数据类型
在讲stringify之前,我们先复习一下js有哪些数据类型:
- Number
- String
- Boolean
- BigInt
- Null
- undefined
- Symbol
- Object
而其中Object作为引用数据类型,其中又包括:
- Function
- Array
- Date
- RegExp
- Math
那么上述的数据类型,我们都是通过什么方式来判断一个数据是什么数据类型的呢?那么接下来我们再复习一下
typeof 以及 instanceOf
- typeof
我们一般用typeof来判断一个数据的基础数据类型,因为typeof除了能够判断出function之外,其他的都会被识别为object,因此typeof是没有办法单独帮我们判断出当前数据是什么数据类型的
- instanceof
instanceof和typeof恰好相互补,它能够正确的判断出复杂数据类型,而对基础数据类型却不够清晰,因此,我们在判断数据类型的时候,也经常会用两者的结合
不过今天就不在这里过多介绍了,因为今天的目标主要是实现JSON.stringify方法,有以上的复习就足够了
JSON.stringify
我们在和后端传输数据的时候,经常会通过JSON.parse和JSON.stringify来将后端传给我们的数据和我们常诶后端的数据变成json数据,那么这其中到底是怎么实现的呢?
官方对stringfy的定义
我们先来看一下,在MDN上面对stringify这个方法的定义以及它的作用
JSON.stringify(value[, replacer [, space]])
我们发现实际上这个方法有三个参数,其中value作为需要JSON化的值,而replacer相当于是一个前置处理,如果提供一个函数,那么需要被处理的值的属性先经过这个函数处理;而如果是一个数组的话,就会只处理这个数组中包含的属性,相当于选择性处理;第三个space就不说了。
我们今天主要实现的就是我们日常使用的JSON.stringify(value)这样的一个操作,我们在看一下官方在经过这个输出之后会得到什么样的结果。
因为上面的文字太多,我这里用一个表格来做一个相应的处理,大家能更好的理解一下
| 原始类型 | 转化类型 |
|---|---|
| boolean | "false"或者"true" |
| string | string |
| number | "number" |
| symbol | undefined |
| undefined | undefined |
| null/ NaN/ Infinity | "null" |
| undefined/function/symbol(出现在非数组对象的属性值中) | 忽略 |
| undefined/function/symbol(出现在Array中) | null |
| Date | Date.toJSON()是字符串,因此当作字符串处理 |
| function | undefined |
| object | 如果有toJSON方法,就直接toJSON处理;如果包含symbol属性,则会被忽略;包含循环引用对象,抛出错误 |
看了上面的表格,我们对JSON.stringfy这个方法做了什么事情就有了一个比较清晰的认知,那么接下来就让我们一起实现一个JSON.stringify方法吧!
实现一个JSON.stringify方法
// 其实就是根据需要被转化数据的数据类型来做相应的处理,因此我们需要判断他们的类型
function itemToJSON(item) {
// 定一个一个参数作为类型以及结果
let itemType = typeof item,
result
// 首先是基础类型
if (itemType !== 'object') {
if (item == Infinity || Number.isNaN(item)) {
// 如果是无穷或者NaN,就返回'null'
result = "null"
} else if (itemType === 'string') {
// 如果是字符串,返回其本身
result = '"' +item+ '"'
} else if (itemType === 'symbol' || itemType === 'undefined' || itemType === 'function') {
// 如果是undefined/function/symbol,返回undefined
result = undefined
} else if (itemType === 'number' || itemType === 'boolean') {
// 如果是number或者布尔类型,就返回字符串形式的item
result = "" + item + ""
}
return result
} else if (itemType === 'object') {
// 如果是Object类型的
if (item === null) {
// 首先判断是不是null
result = "null"
return result
} else if (item.toJSON && typeof item.toJSON === 'function') {
// 再判断是不是有toJSON方法,有的话就在对他进行JSON处理
result = itemToJSON(item.toJSON())
return result
} else if (item instanceof Array) {
result = []
// 如果是数组的话,需要对其中的每一个值进行判断
item.forEach((item, index) => {
if (typeof item == undefined || typeof item == 'function' || typeof item == 'symbol') {
result[index] = null
} else {
result[index] = itemToJSON(item)
}
})
return ("[" + result + "]")
} else {
result = []
// 对象object
Object.keys(item).forEach((it, index) => {
if (typeof it !== 'symbol') {
// 对象中出现symbol属性就被忽略
if (typeof item[it] !== 'undefined' && typeof item[it] !== 'function' &&
typeof item[it] !== 'symbol') {
// 这三种类型在属性值中是应该被忽略的
result.push('"' + it + '"' + ":" + itemToJSON(item[it]))
}
}
})
return ("{" + result + "}")
}
}
}
写完了上面的判断,其实有部分还是没有的,就是循环引用抛出错误的部分,另外正则部分其实走的是object分支,然后Date也进行了判断。然后NaN其实是number类型的,因此他应该在最前面判断,否则的话会走number那一条分支,这个也要注意一下。
结语
总的来说其实就是根据所列的规则来一个个判断下来,如果你一步步写来下的话,难度其实还好,关键是需要熟练的运用typeof和instanceof,需要我们对数据类型的有比较熟悉的认知,大家也可以一起写一遍,通过例子,一步步去看这一步是不是对的,我自己电脑上验证了一下,基本的都能判断出来,如果有错误的话,也欢迎讨论!