时间:2022-09-14 09:15:48 | 栏目:JavaScript代码 | 点击:次
JSON.stringify是一个使用非常高频的API,但是其却存在一个特性,我们在使用的过程中需要留意这些特性以避免为代码程序埋雷,那么接下来便一起动手实现一个简易版本的jsonStringify函数
布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值
现在有这么一个对象:
const obj = { bol: new Boolean(true), num: new Number(1), str: new String(1) }
利用typeof检测obj各个属性的数据类型
typeof obj.bol; // object typeof obj.num; // object typeof obj.str; // object
将其序列化stringify之后
JSON.stringify(obj); // {"bol":true,"num":1,"str":"1"}
此时再将其解析parse进行各个属性的数据类型
const stringifyObj = JSON.parse(JSON.stringify(obj)); typeof stringifyObj.bol; // boolean typeof stringifyObj.num; // number typeof stringifyObj.str; // string
NaN、Infinity、-Infinity以及null在序列化stringify时都会被当作null
const obj = { nan: NaN, infinity: Infinity, null: null, }; JSON.stringify(obj); // {"nan":null,"infinity":null,"null":null}
对象在序列化的时候,若是其存在toJSON函数,这个函数返回的值就是整个对象序列化后的结果
const obj = { nan: NaN, infinity: Infinity, null: null, toJSON() { return "拥有toJSON函数"; }, }; JSON.stringify(obj); // "拥有toJSON函数"
可以看到序列化之后的数据仅存在toJSON函数的返回值,其余数据全部忽略
⚠️:Date数据会被正常序列化,因为Date上部署了toJSON函数,可以通过控制台打印Date.prototype.toJSON得知
const obj = { date: new Date(), }; JSON.stringify(obj); // {"date":"2021-10-08T11:43:31.881Z"}
表现不一的undefined、function和symbol
作为对象键值对时:
作为值:
const obj = { undefined: undefined, fn() {}, symbol: Symbol() }; JSON.stringify(obj); // {}
作为键:
const fn = function () {}; const obj = { [undefined]: undefined, [fn]: function () {}, [Symbol()]: Symbol() }; JSON.stringify(obj); // {}
undefined、function和symbol作为对象的key和value时,会在序列化时将其忽略
⚠️:此时可能会改变对象原有的顺序,因为上述三种数据会在序列化时被忽略
作为数组值时:
const arr = [undefined, function fn() {}, Symbol()]; JSON.stringify(arr); // [null,null,null]
undefined、function和symbol作为数组的value时,会在序列化时将其都转换为null
单独存在时:
JSON.stringify(undefined); // undefined JSON.stringify(function () {}); // undefined JSON.stringify(Symbol()); // undefined
undefined、function和symbol单独存在时,会在序列化时都转换为undefined
序列化过程中,仅会序列化可枚举属性,不可枚举属性将会忽视
const obj = { name: "nordon", age: 18, }; // 将age修改为不可枚举属性 Object.defineProperty(obj, "age", { enumerable: false, }); JSON.stringify(obj); // {"name":"nordon"}
⚠️:此举也会改变对象的原有顺序
循环引用的对象,会在序列化时抛出异常
const obj = { name: "nordon", age: 18, }; const p = { name: 'wy', obj } obj.p = p JSON.stringify(obj);
此时会导致控制台抛出异常:
Uncaught TypeError: Converting circular structure to JSON --> starting at object with constructor 'Object' | property 'p' -> object with constructor 'Object' --- property 'obj' closes the circle at JSON.stringify (<anonymous>)
明白了JSON.stringify的一些特性,接下来便可以依据这些特性动手实现一个kack版本
在动手实现之前,先利用柯里化封装一些数据类型校验的工具函数:
const currying = (fn, ...outParams) => { // 获取 fn 函数需要的参数个数 const paramsLen = fn.length; return (...args) => { // 收集全部参数 let params = [...outParams, ...args]; // 若参数没有达到 fn 需要的参数,继续收集参数 if (params.length < paramsLen) { return currying(fn, ...params); } return fn(...params); }; }; /** * type: 类型 - [object Array]、[object Number]等 * source: 数据源 */ const judgeType = (type, source) => { return Object.prototype.toString.call(source) === type; }; const isUndefined = currying(judgeType, "[object Undefined]"); const isSymbol = currying(judgeType, "[object Symbol]"); const isFunction = currying(judgeType, "[object Function]"); const isObject = currying(judgeType, "[object Object]"); const isNull = currying(judgeType, "[object Null]");
下面直接上代码:
function jsonStringify(data) { let type = typeof data; if (isNull(data)) { // null 直接返回 字符串'null' return "null"; } else if (data.toJSON && typeof data.toJSON === "function") { // 配置了 toJSON函数, 直接使用 toJSON 返回的数据且忽略其他数据 return jsonStringify(data.toJSON()); } else if (Array.isArray(data)) { let result = []; //如果是数组,那么数组里面的每一项类型又有可能是多样的 data.forEach((item, index) => { if (isUndefined(item) || isSymbol(item) || isFunction(item)) { result[index] = "null"; } else { result[index] = jsonStringify(item); } }); result = "[" + result + "]"; return result.replace(/'/g, '"'); } else if (isObject(data)) { // 处理普通对象 let result = []; Object.keys(data).forEach((item, index) => { if (typeof item !== "symbol") { //key 如果是 symbol 对象,忽略 if ( data[item] !== undefined && typeof data[item] !== "function" && typeof data[item] !== "symbol" ) { //键值如果是 undefined、function、symbol 为属性值,忽略 result.push( '"' + item + '"' + ":" + jsonStringify(data[item]) ); } } }); return ("{" + result + "}").replace(/'/g, '"'); } else if (type !== "object") { let result = data; //data 可能是基础数据类型的情况在这里处理 if (Number.isNaN(data) || data === Infinity) { //NaN 和 Infinity 序列化返回 "null" result = "null"; } else if (isUndefined(data) || isSymbol(data) || isFunction(data)) { // 由于 function 序列化返回 undefined,因此和 undefined、symbol 一起处理 return undefined; } else if (type === "string") { result = '"' + data + '"'; } return String(result); } }
至此简易版本的JSON.stringify完成,虽然能力尚欠缺许多,主要是提供一个思路,核心注释已注释在代码中,可结合代码和上文的特性一起理解