Подтвердить что ты не робот

Как я могу сериализовать функцию в JavaScript?

Например, скажем, у меня есть функция, определенная следующим образом:

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

Я хочу иметь возможность сериализовать эту функцию и сохранить ее с помощью localStorage. Как я могу это сделать?

4b9b3361

Ответ 1

Большинство браузеров (Chrome, Safari, Firefox и другие) возвращают определение функций из метода .toString():

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

Просто будьте осторожны, потому что родные функции не будут правильно сериализованы. Например:

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

Ответ 2

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

Сериализация

var storedFunction = foo.toString();

десериализации

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

объяснение

foo.toString() будет строковой версией функции foo

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

Но new Function принимает тело функции, а не саму функцию.

См. MDN: Функция

Таким образом, мы можем создать функцию, которая возвращает нам эту функцию и присвоить ее некоторой переменной.

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

Так что теперь, когда мы передаем эту строку в конструктор, мы получаем функцию и немедленно выполняем ее, чтобы вернуть нашу исходную функцию. :)

Ответ 3

Я сделал этот ответ, чтобы устранить некоторые довольно серьезные недостатки существующих ответов: .toString()/eval() и new Function() сами по себе не будут работать вообще, если ваша функция использует this или именованные аргументы (function (named, arg) {}) соответственно.

Используя toJSON() ниже, все, что вам нужно сделать, это вызвать JSON.stringify() как обычно для функции и использовать Function.deserialise при parse() ing.

Следующее не будет работать для кратких функций (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
    ;
};

Посмотрите на ДЕМО

У него самое простое:

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

Более полезно, вы можете сериализовать целые объекты, содержащие функции, и вытащить их обратно:

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));

Выходы:

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

Я не тестировал старые IE, но он работает на IE11, FF, Chrome, Edge.

Примечание: name функции потеряно, если вы используете это свойство, то на самом деле вы ничего не можете сделать.
Вы можете изменить его, чтобы не использовать prototype легко, но это для вас, если это то, что вам нужно.

Ответ 4

Если вам нужен был способ сериализации функций стрелок в ES6, я написал сериализатор, который заставляет все работать.

Все, что вам нужно сделать, это вызвать JSON.stringify() как обычно для функции или объекта, содержащего функцию, и вызвать Function.deserialise с другой стороны, чтобы магия работала.

Очевидно, вы не должны ожидать, что замыкания будут работать, в конце концов, это сериализация, но значения по умолчанию, деструктуризация, this, arguments, функции-члены class все это будет сохранено.
Если вы используете только обозначения ES5, пожалуйста, используйте мой другой ответ.Этот действительно выше и вне


Здесь демонстрация

Работаем в Chrome/Firefox/Edge.
Сильфон - это результат демонстрации; несколько функций, сериализованная строка, затем вызывается новая функция, созданная после десериализации.

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

И, наконец, магия

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];
};

Ответ 5

Будучи немного раздражен недостатками JSON, я написал небольшую функцию сериализации, которая правильно обрабатывает сериализацию: функции, null, undefined, NaN и Infinity. Единственное, что он не делает, это сериализует экземпляры классов, так как я не мог придумать, как обойти вызов конструктора снова.

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+';'
    )();
}

Давайте проверим это!

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]);
}

Ответ 6

Не сериализуйте вызов, вместо этого попробуйте сериализовать информацию, позволяя повторить вызов.

Ответ 7

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; 
}"

это не то же самое, что w (3), который почему-то все еще помнит добавить 3.