功能比JSON.stringify强大的stringify(亲测好用)

Posted 乔碧萝·乔斯达

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了功能比JSON.stringify强大的stringify(亲测好用)相关的知识,希望对你有一定的参考价值。

介绍

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

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

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

例子:

    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 = /^async\\s+function/
const asyncGeneratorReg = /^async\\s+\\*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)
}

以上是关于功能比JSON.stringify强大的stringify(亲测好用)的主要内容,如果未能解决你的问题,请参考以下文章

JSON.parse和JSON.stringify的区别

当我 JSON.stringify(object) 我得到一个疯狂的字符串作为值

qs.stringify、qs.parse、JSON.stringify的使用和区别

JSON.parse()和JSON.stringify()用法

QA 环境中缺少 JSON Stringify 但本地没有

如何获取 JSON stringify 的错误消息?