热搜: fiddler git ip 代理
历史搜索

手写一个比JSON.stringify更强大的stringify

游客2024-12-19 12:03:01

写了一个能将对象或数组转换成字符串(内部能转义多种数据类型)的函数,供大家使用。

原理是把值转化为 js 代码字符串,再利用eval()函数把字符串转化为对象或数组,实现深拷贝。

使用场景:localStoragesessionStorage存储及转换,各种局限于只能使用字符串存储或传递对象的场景。

例子:

const obj = {
    a: NaN,
    b: '',
    c: true,
    d(){
        console.log('d')
    },
    e: async function*(){
        consoel.log('e')
    },
    f: class {},
    g: [],
    h: new Map([[{}, []]]),
    i: new Set([1,2,1,3]),
    j: /^j$/g,
    [Symbol('k')]: Symbol.for('k'),
    l: 18n,
    m: Math,
    n: new ArrayBuffer(9)
}

const jsStr = stringify(obj)
console.log(jsStr)
/*
({a: NaN,b: '',c: true,d: function d() {
        console.log('d')
    },e: async function* () {
        consoel.log('e')
    },f: class A{ },g: [],h: new Map([[{}, []]]),i: new Set([1,2,3]),j: /^j$/g,l: 18n,m: Math,n: new ArrayBuffer(9),[Symbol('k')]: Symbol.for('k')})
*/

console.log(eval(jsStr)) // 输出复制后的源对象

以下是源代码(源代码简单易读):

/**
 * 将数组或对象转化为字符串
 */
const typings = {
    number: '[object Number]',
    string: '[object String]',
    boolean: '[object Boolean]',
    symbol: '[object Symbol]',
    bigInt: '[object BigInt]',
    null: '[object Null]',
    undefined: '[object Undefined]',
    object: '[object Object]',
    array: '[object Array]',
    regExp: '[object RegExp]',
    math: '[object Math]',
    map: '[object Map]',
    set: '[object Set]',
    function: '[object Function]',
    generator: '[object GeneratorFunction]',
    async: '[object AsyncFunction]',
    asyncGenerator: '[object AsyncGeneratorFunction]',
    arrayBuffer: '[object ArrayBuffer]'
}
 
const classReg = /^class/
const arrowReg = /=>/
const funcReg = /^function/
const asyncFuncReg = /^asyncs+function/
const asyncGeneratorReg = /^asyncs+*function/
 
/**
 * 主函数
 * @param {object | array} val 
 * @returns {string}
 */
function stringify(val) {
    const type = getType(val)
    // 处理边界
    if (
        type !== typings.object &&
        type !== typings.array
    ) {
        throw new TypeError('Arguments are not arrays or objects')
    }
 
    return '(' + handler(val, type) + ')'
}
 
/**
 * 处理器
 * @param {any} val 
 * @param {string} type 
 * @returns {string}
 */
function handler(val, type) {
    switch (type) {
        case typings.number:
            return createNum(val)
        case typings.string:
            return createStr(val)
        case typings.boolean:
            return createBool(val)
        case typings.null:
            return createNull()
        case typings.undefined:
            return createUndefined()
        case typings.bigInt:
            return createBigInt(val)
        case typings.symbol:
            return createSymbol(val)
        case typings.function:
            return createFunc(val)
        case typings.generator:
            return createGenerator(val)
        case typings.async:
            return createAsync(val)
        case typings.asyncGenerator:
            return createAsyncGenerator(val)
        case typings.object:
            return createObj(val)
        case typings.array:
            return createArr(val)
        case typings.map:
            return createMap(val)
        case typings.set:
            return createSet(val)
        case typings.regExp:
            return createRegExp(val)
        case typings.math:
            return createMath()
        case typings.arrayBuffer:
            return createBuffer(val)
        default:
            return
    }
}
 
/**
 * 创建函数
 */
function createNum(num) {
    return num
}
 
function createStr(str) {
    return `'${str}'`
}
 
function createBool(bool) {
    return bool ? 'true' : 'false'
}
 
function createNull() {
    return 'null'
}
 
function createUndefined() {
    return 'undefined'
}
 
function createBigInt(bigInt) {
    return bigInt.toString() + 'n'
}
 
function createSymbol(symbol) {
    const description = symbol.description
    const isFor = Symbol.for(description) === symbol
 
    function isVoid(val) {
        return val === undefined || val === ''
    }
    return isFor ? `Symbol.for(${isVoid(description) ? '' : `'${description}'`})` : `Symbol(${isVoid(description) ? '' : `'${description}'`})`
}
 
function createFunc(func) {
    const funcStr = func.toString()
 
    if (funcReg.test(funcStr) || arrowReg.test(funcStr) || classReg.test(funcStr)) {
        return funcStr
    } else {
        return `function ${funcStr}`
    }
}
 
function createGenerator(generator) {
    const generatorStr = generator.toString()
 
    return funcReg.test(generatorStr) ? generatorStr : `function ${generatorStr}`
}
 
function createAsync(asyncFunc) {
    const asyncFuncStr = asyncFunc.toString()
 
    if (asyncFuncReg.test(asyncFuncStr) || arrowReg.test(asyncFuncStr)) {
        return asyncFuncStr
    } else {
        return asyncFuncStr.replace('async ', 'async function ')
    }
}
 
function createAsyncGenerator(asyncGenerator) {
    const asyncGeneratorStr = asyncGenerator.toString()
 
    return asyncGeneratorReg.test(asyncGeneratorStr) ? asyncGeneratorStr : asyncGeneratorStr.replace('async *', 'async function*')
}
 
function createObj(obj) {
    let start = '{'
    let end = '}'
    let res = ''
 
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
            res += `${key}: ${handler(obj[key], getType(obj[key]))},`
        }
    }
    const symbolList = Object.getOwnPropertySymbols(obj)
    for (const symbol of symbolList) {
        const symbolStr = createSymbol(symbol)
        res += `[${symbolStr}]: ${handler(obj[symbol], getType(obj[symbol]))},`
    }
 
    return start + res.slice(0, -1) + end
}
 
function createArr(arr) {
    let start = '['
    let end = ']'
    let res = ''
 
    for (const item of arr) {
        res += handler(item, getType(item)) + ','
    }
 
    return start + res.slice(0, -1) + end
}
 
function createMap(map) {
    let start = 'new Map(['
    let end = '])'
    let res = ''
    map.forEach((val, key) => {
        res += `[${handler(key, getType(key))}, ${handler(val, getType(val))}],`
    })
 
    return start + res.slice(0, -1) + end
}
 
function createSet(set) {
    let start = 'new Set('
    let end = ')'
 
    return start + createArr([...set]) + end
}
 
function createRegExp(regExp) {
    return regExp
}
 
function createMath() {
    return 'Math'
}
 
function createBuffer(arrayBuffer) {
    return `new ArrayBuffer(${arrayBuffer.byteLength})`
}
 
/**
 * 封装 Object.toString 方法
 * @param {any} val 
 * @returns {string}
 */
function getType(val) {
    return Object.prototype.toString.call(val)
}