例如,假设我有一个定义如下的函数:
function foo() {
return "Hello, serialized world!";
}
我想要将那个函数序列化并使用 localStorage
存储。我该如何实现?
例如,假设我有一个定义如下的函数:
function foo() {
return "Hello, serialized world!";
}
我想要将那个函数序列化并使用 localStorage
存储。我该如何实现?
大多数浏览器(Chrome,Safari,Firefox,可能还有其他浏览器)从.toString()
方法返回函数的定义:
> function foo() { return 42; }
> foo.toString()
"function foo() { return 42; }"
请注意,原生函数不会正确序列化。例如:
> alert.toString()
"function alert() { [native code] }"
function foo(){/* returns 42 */}
。 - RobGfunction 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!';}"
所以现在当我们将这个字符串传递给构造函数时,我们得到一个函数,然后立即执行它以获取我们的原始函数。 :)
.toString()
/ eval()
和new Function()
本身都无法正常工作,如果您的函数使用this
或命名参数(function(named,arg){}
),则更是如此。使用下面的toJSON()
,您只需要像往常一样调用JSON.stringify()
即可在函数上,并在parse()
ing时使用Function.deserialise
。以下内容不适用于简明函数(hello => 'there'
),但对于标准的ES5 fat函数,它将按照定义返回它,当然闭包除外。我的其他答案将使用所有的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
,但如果您需要,请自行处理。
JSON.stringify()
来处理包含该函数的函数或对象,并在另一端调用Function.deserialise
即可实现魔法。this
、arguments
、class
成员函数,所有这些都将被保留。在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
最后,神奇的{{magic}}出现了
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];
};
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。