51 votes

Comment sérialiser une fonction en JavaScript ?

Par exemple, disons que j'ai une fonction définie comme suit :

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

Je veux pouvoir sérialiser cette fonction et la stocker en utilisant localStorage . Comment puis-je m'y prendre ?

37voto

David Wolever Points 34304

La plupart des navigateurs (Chrome, Safari, Firefox, peut-être d'autres) renvoient la définition des fonctions à partir du fichier .toString() méthode :

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

Faites attention car les fonctions natives ne se sérialisent pas correctement. Par exemple :

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

31voto

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

Mise en série de

var storedFunction = foo.toString();

Désérialiser

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

Explication

foo.toString() sera la version chaîne de la fonction foo

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

Mais new Function prend le corps d'une fonction et non la fonction elle-même.

Voir MDN : Fonction

Nous pouvons donc créer une fonction qui nous renvoie cette fonction et l'assigner à une variable.

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

Donc maintenant, lorsque nous passons cette chaîne au constructeur, nous obtenons une fonction et nous l'exécutons immédiatement pour récupérer notre fonction originale :).

13voto

Hashbrown Points 1888

J'ai créé cette réponse pour remédier à certaines lacunes importantes des réponses existantes : .toString() / eval() y new Function() seuls ne fonctionneront pas du tout si votre fonction utilise this ou des arguments nommés ( function (named, arg) {} ), respectivement.

Utilisation de [toJSON()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior) ci-dessous, tout ce que vous devez faire est d'appeler JSON.stringify() comme d'habitude sur la fonction, et utiliser Function.deserialise quand parse() ing .

Ce qui suit ne fonctionnera pas pour les fonctions concises ( hello => 'there' ), mais pour les fonctions ES5 standard, il les retournera telles qu'elles ont été définies, sans tenir compte des fermetures bien sûr. Mon autre réponse fonctionnera avec toutes les qualités de l'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
    ;
};

Jetez un coup d'œil à la DEMO

Dans sa forme la plus simple :

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

Plus utile, vous pouvez sérialiser des objets entiers contenant des fonctions et les extraire à nouveau. :

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

Sorties :

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

Je n'ai pas testé les anciens IE, mais cela fonctionne sur IE11, FF, Chrome, Edge.

NB, le <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name" rel="noreferrer"><code>name</code></a> de la fonction est perdue, si vous utilisez cette propriété, il n'y a rien que vous puissiez faire, vraiment.<br>Vous pouvez le changer pour ne pas utiliser <code>prototype</code> facilement, mais c'est à vous de le faire si c'est ce dont vous avez besoin.

7voto

Hashbrown Points 1888

Si vous aviez besoin d'un moyen de sérialiser Fonctions des flèches en ES6, j'ai écrit un sérialiseur qui fait tout fonctionner.

Il vous suffit d'appeler JSON.stringify() comme d'habitude sur la fonction ou l'objet contenant la fonction, et appeler Function.deserialise en l'autre côté pour que la magie opère.

Évidemment, il ne faut pas s'attendre à ce que les fermetures fonctionnent, il s'agit de sérialisation après tout, mais les défauts, la déstructuration, <code>this</code> , <code>arguments</code> , <code>class</code> les fonctions membres, tout sera préservé.<br>Si vous n'utilisez que des notations ES5, utilisez simplement <a href="https://stackoverflow.com/a/51122739/2518317">mon autre réponse </a>. Celui-ci est vraiment au-dessus et au-delà


Voici la démonstration

Travailler dans Chrome/Firefox/Edge.
Voici le résultat de la démo ; quelques fonctions, la chaîne sérialisée, puis l'appel de la nouvelle fonction créée après la désérialisation.

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

Et enfin, la magie

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

-1voto

user1514042 Points 743

Ne sérialisez pas l'appel, mais essayez plutôt de sérialiser l'information, permettant de répéter l'appel, qui peut inclure des choses comme les noms de classe et de méthode, les arguments passés dans l'appel ou simplement un nom de scénario d'appel.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X