How can I serialize a function in JavaScript?

后端 未结 7 1114
Happy的楠姐
Happy的楠姐 2020-12-05 01:59

For example, say I have a function defined as follows:

function foo() {
  return \"Hello, serialized world!\";
}

I want to be able to seria

7条回答
  •  無奈伤痛
    2020-12-05 02:35

    If you needed a way to serialise Arrow Functions in ES6 I have written a serialiser that makes everything work.

    All you need to do is call JSON.stringify() as usual on the function or object containing the function, and call Function.deserialise on the other side for the magic to work.

    Obviously you shouldn't expect closures to work, it is serialisation after all, but defaults, destructuring, this, arguments, class member functions, it'll all be preserved.
    If you're only using ES5 notations please just use my other answer. This one really is above and beyond


    Here's the demonstration

    Working in Chrome/Firefox/Edge.
    Bellow is the output from the demo; a few functions, the serialised string, then calling the new function created after deserialisation.

    test = {
        //make the function
        run : function name(x, y, z) { return this.a + x + y + z; },
        a : 2
    };
    //serialise it, see what it looks like
    test = JSON.stringify(test) //{"run":["window.Function",["x","y","z"],"return this.a + x + y + z;"],"a":2}
    test = JSON.parse(test, Function.deserialise)
    //see if `this` worked, should be 2+3+4+5 : 14
    test.run(3, 4, 5) //14
    
    test = () => 7
    test = JSON.stringify(test) //["window.Function",[""],"return 7"]
    JSON.parse(test, Function.deserialise)() //7
    
    test = material => material.length
    test = JSON.stringify(test) //["window.Function",["material"],"return material.length"]
    JSON.parse(test, Function.deserialise)([1, 2, 3]) //3
    
    test = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c
    test = JSON.stringify(test) //["window.Function",["[a, b] = [1, 2]","{ x: c } = { x: a + b }"],"return a + b + c"]
    JSON.parse(test, Function.deserialise)([3, 4]) //14
    
    class Bob {
        constructor(bob) { this.bob = bob; }
        //a fat function with no `function` keyword!!
        test() { return this.bob; }
        toJSON() { return {bob:this.bob, test:this.test} }
    }
    test = new Bob(7);
    test.test(); //7
    test = JSON.stringify(test); //{"bob":7,"test":["window.Function",[""],"return this.bob;"]}
    test = JSON.parse(test, Function.deserialise);
    test.test(); //7
    

    And finally, the magic

    Function.deserialise = function(key, data) {
        return (data instanceof Array && data[0] == 'window.Function') ?
            new (Function.bind.apply(Function, [Function].concat(data[1], [data[2]]))) :
            data
        ;
    };
    Function.prototype.toJSON = function() {
        var whitespace = /\s/;
        var pair = /\(\)|\[\]|\{\}/;
    
        var args = new Array();
        var string = this.toString();
    
        var fat = (new RegExp(
            '^\s*(' +
            ((this.name) ? this.name + '|' : '') +
            'function' +
            ')[^)]*\\('
        )).test(string);
    
        var state = 'start';
        var depth = new Array(); 
        var tmp;
    
        for (var index = 0; index < string.length; ++index) {
            var ch = string[index];
    
            switch (state) {
            case 'start':
                if (whitespace.test(ch) || (fat && ch != '('))
                    continue;
    
                if (ch == '(') {
                    state = 'arg';
                    tmp = index + 1;
                }
                else {
                    state = 'singleArg';
                    tmp = index;
                }
                break;
    
            case 'arg':
            case 'singleArg':
                var escaped = depth.length > 0 && depth[depth.length - 1] == '\\';
                if (escaped) {
                    depth.pop();
                    continue;
                }
                if (whitespace.test(ch))
                    continue;
    
                switch (ch) {
                case '\\':
                    depth.push(ch);
                    break;
    
                case ']':
                case '}':
                case ')':
                    if (depth.length > 0) {
                        if (pair.test(depth[depth.length - 1] + ch))
                            depth.pop();
                        continue;
                    }
                    if (state == 'singleArg')
                        throw '';
                    args.push(string.substring(tmp, index).trim());
                    state = (fat) ? 'body' : 'arrow';
                    break;
    
                case ',':
                    if (depth.length > 0)
                        continue;
                    if (state == 'singleArg')
                        throw '';
                    args.push(string.substring(tmp, index).trim());
                    tmp = index + 1;
                    break;
    
                case '>':
                    if (depth.length > 0)
                        continue;
                    if (string[index - 1] != '=')
                        continue;
                    if (state == 'arg')
                        throw '';
                    args.push(string.substring(tmp, index - 1).trim());
                    state = 'body';
                    break;
    
                case '{':
                case '[':
                case '(':
                    if (
                        depth.length < 1 ||
                        !(depth[depth.length - 1] == '"' || depth[depth.length - 1] == '\'')
                    )
                        depth.push(ch);
                    break;
    
                case '"':
                    if (depth.length < 1)
                        depth.push(ch);
                    else if (depth[depth.length - 1] == '"')
                        depth.pop();
                    break;
                case '\'':
                    if (depth.length < 1)
                        depth.push(ch);
                    else if (depth[depth.length - 1] == '\'')
                        depth.pop();
                    break;
                }
                break;
    
            case 'arrow':
                if (whitespace.test(ch))
                    continue;
                if (ch != '=')
                    throw '';
                if (string[++index] != '>')
                    throw '';
                state = 'body';
                break;
    
            case 'body':
                if (whitespace.test(ch))
                    continue;
                string = string.substring(index);
    
                if (ch == '{')
                    string = string.replace(/^{\s*(.*)\s*}\s*$/, '$1');
                else
                    string = 'return ' + string.trim();
    
                index = string.length;
                break;
    
            default:
                throw '';
            }
        }
    
        return ['window.Function', args, string];
    };
    

提交回复
热议问题