Как я могу сериализовать функцию в JavaScript? - PullRequest
29 голосов
/ 13 сентября 2011

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

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

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

Ответы [ 6 ]

26 голосов
/ 13 сентября 2011

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

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

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

> alert.toString()
"function alert() { [native code] }"
7 голосов
/ 14 августа 2017
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!';}"

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

2 голосов
/ 01 июля 2018

Я сделал этот ответ, чтобы устранить некоторые довольно большие недостатки с существующими ответами: .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
    ;
};

Взгляните на DEMO

На самом простом:

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.

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

0 голосов
/ 09 апреля 2019

Будучи немного раздраженным из-за недостатков 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]);
}
0 голосов
/ 01 июля 2018

Если вам нужен способ сериализации Функции стрелок в 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];
};
0 голосов
/ 27 марта 2018
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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...