js实现JSON.stringify的方法实例代码
作者:zhengzhengwang
在大厂的前端面试过程中,JSON.Stringify常常被问到,那么能够熟练的运用,掌握就必不可少,下面这篇文章主要介绍了js实现JSON.stringify的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
实现一个自定义的 JSON.stringify 函数,并详细解释其实现原理和边界情况处理。
1. 基础版本实现
function myJSONStringify(data) {
// 处理基本类型
if (typeof data === 'string') {
return `"${data}"`;
}
if (typeof data === 'number' || typeof data === 'boolean') {
return String(data);
}
if (data === null) {
return 'null';
}
if (data === undefined || typeof data === 'function' || typeof data === 'symbol') {
return undefined; // 这些类型在对象中会被忽略,在数组中会转为null
}
// 处理数组
if (Array.isArray(data)) {
const arrValues = data.map(item => {
const value = myJSONStringify(item);
return value === undefined ? 'null' : value;
});
return `[${arrValues.join(',')}]`;
}
// 处理普通对象
if (typeof data === 'object') {
// 处理循环引用
if (hasCircularReference(data)) {
throw new TypeError('Converting circular structure to JSON');
}
const keys = Object.keys(data);
const objValues = keys.map(key => {
const value = myJSONStringify(data[key]);
// 忽略值为 undefined、函数、symbol 的属性
if (value === undefined) {
return null;
}
return `"${key}":${value}`;
}).filter(item => item !== null);
return `{${objValues.join(',')}}`;
}
}2. 完整的增强版本
class JSONStringify {
constructor() {
this.cache = new Set(); // 用于检测循环引用
}
stringify(data) {
this.cache.clear();
return this._stringify(data);
}
_stringify(data) {
// 处理基本类型
if (typeof data === 'string') {
return this._escapeString(data);
}
if (typeof data === 'number') {
// 处理特殊数值
if (Number.isNaN(data) || !Number.isFinite(data)) {
return 'null';
}
return String(data);
}
if (typeof data === 'boolean') {
return String(data);
}
if (data === null) {
return 'null';
}
if (data === undefined || typeof data === 'function' || typeof data === 'symbol') {
return undefined;
}
// 处理 BigInt (JSON.stringify 会报错)
if (typeof data === 'bigint') {
throw new TypeError('Do not know how to serialize a BigInt');
}
// 检查循环引用
if (this.cache.has(data)) {
throw new TypeError('Converting circular structure to JSON');
}
this.cache.add(data);
// 处理 Date 对象
if (data instanceof Date) {
return `"${data.toISOString()}"`;
}
// 处理 RegExp 对象
if (data instanceof RegExp) {
return '{}';
}
// 处理数组
if (Array.isArray(data)) {
const result = this._stringifyArray(data);
this.cache.delete(data);
return result;
}
// 处理普通对象
if (typeof data === 'object') {
const result = this._stringifyObject(data);
this.cache.delete(data);
return result;
}
}
_stringifyArray(arr) {
const values = arr.map((item, index) => {
// 处理数组中的空位
if (index in arr) {
const value = this._stringify(item);
return value === undefined ? 'null' : value;
}
return 'null'; // 数组空位转为 null
});
return `[${values.join(',')}]`;
}
_stringifyObject(obj) {
const keys = Object.keys(obj);
const pairs = [];
for (const key of keys) {
// 处理 Symbol 键(JSON.stringify 会忽略 Symbol 键)
if (typeof key === 'symbol') {
continue;
}
const value = this._stringify(obj[key]);
// 忽略值为 undefined、function、symbol 的属性
if (value !== undefined) {
pairs.push(`"${this._escapeString(key)}":${value}`);
}
}
// 处理 toJSON 方法
if (obj.toJSON && typeof obj.toJSON === 'function') {
const jsonValue = obj.toJSON();
if (jsonValue !== undefined) {
return this._stringify(jsonValue);
}
}
return `{${pairs.join(',')}}`;
}
_escapeString(str) {
// 处理特殊字符
const escapeMap = {
'"': '\\"',
'\\': '\\\\',
'\b': '\\b',
'\f': '\\f',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t'
};
return str.replace(/["\\\b\f\n\r\t]/g, match => escapeMap[match]);
}
}
// 循环引用检测函数
function hasCircularReference(obj, seen = new Set()) {
if (obj === null || typeof obj !== 'object') {
return false;
}
if (seen.has(obj)) {
return true;
}
seen.add(obj);
if (Array.isArray(obj)) {
for (const item of obj) {
if (hasCircularReference(item, seen)) {
return true;
}
}
} else {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if (hasCircularReference(obj[key], seen)) {
return true;
}
}
}
}
seen.delete(obj);
return false;
}3. 处理 replacer 参数
function myJSONStringifyWithReplacer(data, replacer, space) {
const jsonStringify = new JSONStringify();
// 处理 replacer 参数
if (replacer) {
if (typeof replacer === 'function') {
return _stringifyWithFunctionReplacer(data, replacer, space);
} else if (Array.isArray(replacer)) {
return _stringifyWithArrayReplacer(data, replacer, space);
}
}
// 处理 space 参数
const result = jsonStringify.stringify(data);
if (space !== undefined) {
return _formatWithSpace(result, space);
}
return result;
}
function _stringifyWithFunctionReplacer(data, replacer, space) {
const seen = new Set();
function stringify(value, key) {
// 处理循环引用
if (value !== null && typeof value === 'object') {
if (seen.has(value)) {
throw new TypeError('Converting circular structure to JSON');
}
seen.add(value);
}
// 应用 replacer 函数
const replacedValue = replacer.call(data, key, value);
let result;
if (typeof replacedValue === 'string') {
result = `"${replacedValue}"`;
} else if (typeof replacedValue === 'number' || typeof replacedValue === 'boolean') {
result = String(replacedValue);
} else if (replacedValue === null) {
result = 'null';
} else if (Array.isArray(replacedValue)) {
result = '[' + replacedValue.map((item, index) =>
stringify(item, String(index))).join(',') + ']';
} else if (typeof replacedValue === 'object') {
const keys = Object.keys(replacedValue);
const pairs = keys.map(k => {
const val = stringify(replacedValue[k], k);
return `"${k}":${val}`;
});
result = '{' + pairs.join(',') + '}';
} else {
result = undefined;
}
if (value !== null && typeof value === 'object') {
seen.delete(value);
}
return result;
}
return stringify(data, '');
}
function _stringifyWithArrayReplacer(data, replacer, space) {
const replacerSet = new Set(replacer);
function filterObject(obj) {
if (Array.isArray(obj)) {
return obj.map(item =>
item !== null && typeof item === 'object' ? filterObject(item) : item
);
}
if (obj !== null && typeof obj === 'object') {
const filtered = {};
for (const key in obj) {
if (replacerSet.has(key)) {
filtered[key] = obj[key];
}
}
return filtered;
}
return obj;
}
const filteredData = filterObject(data);
const jsonStringify = new JSONStringify();
const result = jsonStringify.stringify(filteredData);
if (space !== undefined) {
return _formatWithSpace(result, space);
}
return result;
}4. 处理 space 参数(格式化)
function _formatWithSpace(jsonStr, space) {
if (space === 0) return jsonStr;
let indentLevel = 0;
let formatted = '';
let inString = false;
// 确定缩进字符串
const indentStr = typeof space === 'number'
? ' '.repeat(Math.min(space, 10))
: space.slice(0, 10);
for (let i = 0; i < jsonStr.length; i++) {
const char = jsonStr[i];
if (char === '"' && jsonStr[i - 1] !== '\\') {
inString = !inString;
formatted += char;
continue;
}
if (inString) {
formatted += char;
continue;
}
switch (char) {
case '{':
case '[':
formatted += char + '\n' + indentStr.repeat(++indentLevel);
break;
case '}':
case ']':
formatted += '\n' + indentStr.repeat(--indentLevel) + char;
break;
case ',':
formatted += ',\n' + indentStr.repeat(indentLevel);
break;
case ':':
formatted += ': ';
break;
default:
formatted += char;
}
}
return formatted;
}5. 使用示例
// 测试数据
const testData = {
name: "张三",
age: 25,
isStudent: true,
hobbies: ["读书", "游泳", null],
address: {
city: "北京",
district: "朝阳区"
},
birthday: new Date('2000-01-01'),
empty: null,
undef: undefined, // 会被忽略
func: function() {}, // 会被忽略
[Symbol('id')]: 123 // 会被忽略
};
// 创建循环引用
testData.self = testData;
// 测试
try {
console.log("普通对象:", myJSONStringify(testData));
} catch (e) {
console.log("循环引用错误:", e.message);
}
// 移除循环引用后测试
delete testData.self;
console.log("普通对象:", myJSONStringify(testData));
console.log("原生 JSON.stringify:", JSON.stringify(testData));
// 测试 replacer 函数
const jsonWithReplacer = myJSONStringifyWithReplacer(
testData,
(key, value) => {
if (key === 'password') return undefined;
if (typeof value === 'string') return value.toUpperCase();
return value;
}
);
console.log("带 replacer:", jsonWithReplacer);
// 测试格式化输出
console.log("格式化输出:", myJSONStringifyWithReplacer(testData, null, 2));6. 特性对比
| 特性 | 原生 JSON.stringify | 我们的实现 |
|---|---|---|
| 基本类型转换 | ✅ | ✅ |
| 数组处理 | ✅ | ✅ |
| 对象处理 | ✅ | ✅ |
| 循环引用检测 | ✅ | ✅ |
| Date 对象 | ✅ | ✅ |
| RegExp 对象 | {} | {} |
| toJSON 方法 | ✅ | ✅ |
| replacer 函数 | ✅ | ✅ |
| replacer 数组 | ✅ | ✅ |
| space 格式化 | ✅ | ✅ |
| Symbol 键 | 忽略 | 忽略 |
| undefined 值 | 忽略/转为 null | 忽略/转为 null |
| BigInt | 报错 | 报错 |
这个实现涵盖了 JSON.stringify 的主要特性,包括:
基本类型转换
对象和数组的递归处理
循环引用检测
特殊类型处理(Date、RegExp)
replacer 参数支持
space 格式化支持
总结
到此这篇关于js实现JSON.stringify的文章就介绍到这了,更多相关js实现JSON.stringify内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
