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 ?
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 ?
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] }"
function foo() {
alert('native function');
return 'Hello, serialised world!';
}
var storedFunction = foo.toString();
var actualFunction = new Function('return ' + foo.toString())()
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 :).
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.
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à
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];
};
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.