如何在 JavaScript 中序列化函数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7395686/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 02:07:45  来源:igfitidea点击:

How can I serialize a function in JavaScript?

javascripthtmlserializationlocal-storage

提问by Akash Gupta

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

例如,假设我有一个定义如下的函数:

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

I want to be able to serialize that function and store it using localStorage. How can I go about doing that?

我希望能够序列化该函数并使用localStorage. 我该怎么做呢?

采纳答案by David Wolever

Most browsers (Chrome, Safari, Firefox, possibly others) return the definition of functions from the .toString()method:

大多数浏览器(Chrome、Safari、Firefox,可能还有其他)从该.toString()方法返回函数的定义:

> function foo() { return 42; }
> foo.toString()
"function foo() { return 42; }"

Just be careful because native functions won't serialize properly. For example:

请小心,因为本机函数不会正确序列化。例如:

> alert.toString()
"function alert() { [native code] }"

回答by Harry

function foo() {
  alert('native function');
  return 'Hello, serialised world!';
}

Serializing

序列化

var storedFunction = foo.toString();

Deserializing

反序列化

var actualFunction = new Function('return ' + foo.toString())()

Explanation

解释

foo.toString() will be string version of the function foo

foo.toString() 将是函数 foo 的字符串版本

"function foo() { ... return 'Hello, serialised world!';}"

But new Functiontakes the body of a function and not the function itself.

但是new Function取函数体而不是函数本身。

See MDN: Function

MDN:函数

So we can create a function that returns us back this function and assign it to some variable.

所以我们可以创建一个函数来返回这个函数并将它分配给某个变量。

"return function foo() { ... return 'Hello, serialised world!';}"

So now when we pass this string to the constructor we get a function and we immediately execute it to get back our original function. :)

所以现在当我们将这个字符串传递给构造函数时,我们得到了一个函数,我们立即执行它以取回我们原来的函数。:)

回答by Hashbrown

I made this answer to address some pretty big flaws with the existing answers: .toString()/eval()and new Function()on their own wont work at all if your function uses thisor named arguments (function (named, arg) {}), respectively.

我提出这个答案是为了解决现有答案的一些相当大的缺陷:.toString()/eval()并且new Function()如果您的函数分别使用this或命名参数 ( function (named, arg) {}),它们将根本无法工作。

Using toJSON()below, all you need to do is call JSON.stringify()as usualon the function, and use Function.deserialisewhen parse()ing.

使用toJSON()下面,您需要做的就是JSON.stringify()像往常一样调用该函数,并Function.deserialiseparse()ing时使用。

The following wont work for concise functions (hello => 'there'), but for standard ES5 fat functions it'll return it as it was defined, closures notwithstanding of course. My other answer will work with all that ES6 goodness.

以下不适用于简明函数 ( hello => 'there'),但对于标准 ES5 胖函数,它将按照定义返回它,当然,尽管有闭包。我的另一个答案将适用于 ES6 的所有优点



Function.prototype.toJSON = function() {
    var parts = this
        .toString()
        .match(/^\s*function[^(]*\(([^)]*)\)\s*{(.*)}\s*$/)
    ;
    if (parts == null)
        throw 'Function form not supported';

    return [
        'window.Function',
        parts[1].trim().split(/\s*,\s*/),
        parts[2]
    ];
};
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
    ;
};


Take a look at the DEMO

看看DEMO

At it's simplest:

最简单的:

var test = function(where) { return 'hello ' + where; };
test = JSON.parse(JSON.stringify(test), Function.deserialise);
console.log(test('there'));
//prints 'hello there'


More usefully, you can serialise entire objects containing functions and pull them back out:

更有用的是,您可以序列化包含函数的整个对象并将它们拉出

test = {
  a : 2,
  run : function(x, y, z) { return this.a + x + y + z; }
};
var serialised = JSON.stringify(test);
console.log(serialised);
console.log(typeof serialised);

var tester = JSON.parse(serialised, Function.deserialise);
console.log(tester.run(3, 4, 5));

Outputs:

输出:

{"a":2,"run":["window.Function",["x","y","z"]," return this.a + x + y + z; "]}
string
14


I didn't test older IE's, but it works on IE11, FF, Chrome, Edge.

我没有测试较旧的 IE,但它适用于 IE11、FF、Chrome、Edge。

NB, the nameof the function is lost, if you use that property then there's nothing you can do, really.
You can change it to not use prototypeeasily, but that's for you to do if that's what you need.

注意,name函数的 丢失了,如果您使用该属性,那么您将无能为力,真的。
您可以将其更改为不易使用prototype,但如果您需要,您可以这样做。

回答by Hashbrown

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

如果您需要一种在 ES6 中序列化箭头函数的方法,我已经编写了一个序列化器,可以让一切正常。

All you need to do is call JSON.stringify()as usualon the function or object containing the function, and call Function.deserialiseon the other sidefor the magic to work.

您需要做的就是JSON.stringify()像往常一样调用包含该函数的函数或对象,并Function.deserialise另一端调用以使魔法起作用。

Obviously you shouldn't expect closures to work, it is serialisation after all, but defaults, destructuring, this, arguments, classmember 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

显然你不应该期望闭包可以工作,毕竟它是序列化,但是默认值、解构、thisargumentsclass成员函数,它们都会被保留下来。
如果您只使用 ES5 符号,请使用我的其他答案。这个真的是超越



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.

在 Chrome/Firefox/Edge 中工作。
Bellow 是演示的输出;一些函数,序列化的字符串,然后调用反序列化后创建的新函数。

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*$/, '');
            else
                string = 'return ' + string.trim();

            index = string.length;
            break;

        default:
            throw '';
        }
    }

    return ['window.Function', args, string];
};

回答by Stuffe

Being a bit annoyed with the shortcomings of JSON i wrote a little serialize function that correctly handles serializing: functions, null, undefined, NaN and Infinity. Only thing it doesn't do it serialize class instances, since I couldn't think of a way to get around calling the constructor again.

对 JSON 的缺点有点恼火,我写了一个小的序列化函数来正确处理序列化:函数、空值、未定义、NaN 和无穷大。唯一它没有序列化类实例的事情,因为我想不出再次调用构造函数的方法。

let serialize = function(input){
    const escape_sequences = {"\\": "\\", "`": "\`", "\\b": "\\b", '"': '\"', "\n": "\n", "\\f": "\\f", "\r": "\r", "\\t": "\\\t", "\\v": "\\v"};
    if(typeof input === "string"){
        let result = input;
        for(var key in escape_sequences){
          result = result.replace(new RegExp(key, "g"), escape_sequences[key]);
        }
        return '`'+result+'`';
    }else if(typeof input === "number"){
        return input.toString();
    }else if(typeof input === "function"){
        // Handle build in functions
        if((/\{\s*\[native code\]\s*\}/).test('' + input)) return input.name;
        return input.toString().replace(/"/g, '\"');
    }else if(typeof input === "symbol"){
        return input.toString();
    }else if(input === null || input === undefined){
        return input;
    }else if(input instanceof Array){
        let res_list = [];
        for(let i = 0; i < input.length; i++){
            res_list.push(serialize(input[i]));
        }
        return "["+res_list.join(",")+"]";
    }else if(input.constructor == Object){
        let res_list = [];
        for(let key in input){
            res_list.push('"'+key.replace(/"/g, '\"')+'":'+serialize(input[key]));
        }   
        return "{"+res_list.join(",")+"}";
    }else if(typeof input === "object"){
        throw(`You are trying to serialize an instance of `+input.constructor.name+`, we don't serialize class instances for a bunch of reasons.`)
    }else{
        return input;
    }
}

let unserialize = function(input){
    return Function(`
        "use strict";
        return `+input+`;`
    )();
}

Lets test it!

让我们测试一下!

let input = {
    'a': "str normal",
    'b"': 'str "quote"',
    'c': 1,
    'd': -1.3,
    'e': NaN,
    'f': -Infinity,
    'g': ()=>123,
    'h': function(){return "lalala"},
    'i': null,
    'j': undefined,
    'k': true,
    'l': Symbol(123),
    'm': [1,2,3],
    'n': [{"a": "str normal",'b"': 'str "quote"','c': 1,'d': -1.3,'e': NaN,'f': -Infinity,'g': ()=>123,'h': function(){return "lalala"},'i': null,'j': undefined,'k': true,'l': Symbol(123),'m': [1,2,3],}],
};

let output = unserialize(serialize(input));

for(let key in input){
    console.log(input[key], output[key]);
}

回答by user1514042

Don't serialize the call, instead try serializing the info, allowing to repeat the call, which can include things like class & method names, arguments passed into the call or just a call scenario name.

不要序列化调用,而是尝试序列化信息,允许重复调用,其中可以包括类和方法名称、传递给调用的参数或只是调用场景名称等内容。

回答by Ben Rayfield

w = (function(x){
    return function(y){ 
        return x+y; 
    };
});""+w returns "function(x){
    return function(y){
        return x+y;
    };
}" but ""+w(3) returns "function(y){
    return x+y; 
}"

which is not the same as w(3) which somehow still remembers to add 3.

这与 w(3) 不同,w(3) 仍然记得添加 3。