例如,假设我的函数定义如下:
function foo() {
return "Hello, serialized world!";
}
我希望能够序列化该函数并使用localStorage
存储它。我怎么能这样做?
答案 0 :(得分:25)
大多数浏览器(Chrome,Safari,Firefox,可能还有其他浏览器)都会从.toString()
方法返回函数定义:
> function foo() { return 42; }
> foo.toString()
"function foo() { return 42; }"
请注意,因为本机函数无法正确序列化。例如:
> alert.toString()
"function alert() { [native code] }"
答案 1 :(得分:6)
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
接受函数的主体而不是函数本身。
因此,我们可以创建一个函数,将函数返回给我们并将其分配给某个变量。
"return function foo() { ... return 'Hello, serialised world!';}"
所以现在当我们将这个字符串传递给构造函数时,我们得到一个函数,然后我们立即执行它以获取原始函数。 :)
答案 2 :(得分:1)
我给出了这个答案,以解决现有答案中的一些相当大的缺陷:如果您的函数使用{{3},.toString()
/ eval()
和new Function()
本身将根本无法工作}或命名参数(function (named, arg) {}
)。
使用下面的this
,您需要做的就是照常调用函数toJSON()
,并在JSON.stringify()
时使用Function.deserialise
。
以下内容对简洁的功能(hello => 'there'
)不起作用,但对于标准ES5胖功能,它将按定义返回它,尽管有闭包。 parse()
ing。
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
;
};
看看My other answer will work with all that ES6 goodness
最简单的方法:
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,该函数的DEMO丢失了,如果您使用该属性,那么您将无能为力。
您可以将其更改为不轻易使用prototype
,但这是您需要的操作。
答案 3 :(得分:0)
如果您需要在ES6中序列化Arrow Functions的方法,我已经编写了一个序列化程序,使所有工作正常。
您需要做的就是照常调用函数JSON.stringify()
或包含该函数的对象,并调用the other side上的Function.deserialise
工作。
很显然,您不应该期望闭包起作用,毕竟它是序列化的,但是默认情况下是解构,this
,arguments
,class
成员函数,它将全部保留下来。
如果您仅使用ES5表示法,请仅使用my other answer。这个真的超越了
在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];
};
答案 4 :(得分:0)
由于有点烦恼JSON的不足,我写了一个可以正确处理序列化的小序列化函数:function,null,undefined,NaN和Infinity。唯一的事情是不会序列化类实例,因为我想不出办法再次调用构造函数。
let serialize = function(input){
if(typeof input === "string"){
return '"'+input.replace(/"/g, '\\"').replace(/\n/g, '\\n')+'"';
}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]);
}
答案 5 :(得分:0)
不序列化调用,而是尝试序列化信息,从而允许重复调用,其中可以包括类和方法名称,传递给调用的参数或只是调用方案名称之类的东西。
答案 6 :(得分:-1)
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)不同,w(3)仍以某种方式记得添加3。