Aka*_*pta 30 javascript html5 serialization local-storage
例如,假设我有一个定义如下的函数:
function foo() {
return "Hello, serialized world!";
}
Run Code Online (Sandbox Code Playgroud)
我希望能够序列化该功能并使用它来存储它localStorage.我怎么能这样做呢?
Dav*_*ver 26
大多数浏览器(Chrome,Safari,Firefox,可能还有其他浏览器)都会从.toString()方法中返回函数的定义:
> function foo() { return 42; }
> foo.toString()
"function foo() { return 42; }"
Run Code Online (Sandbox Code Playgroud)
请小心,因为本机函数不能正确序列化.例如:
> alert.toString()
"function alert() { [native code] }"
Run Code Online (Sandbox Code Playgroud)
Has*_*own 12
我提出这个答案是为了解决现有答案的一些相当大的缺陷:.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
;
};
Run Code Online (Sandbox Code Playgroud)
看看DEMO
最简单的:
var test = function(where) { return 'hello ' + where; };
test = JSON.parse(JSON.stringify(test), Function.deserialise);
console.log(test('there'));
//prints 'hello there'
Run Code Online (Sandbox Code Playgroud)
更有用的是,您可以序列化包含函数的整个对象并将它们拉出:
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));
Run Code Online (Sandbox Code Playgroud)
输出:
{"a":2,"run":["window.Function",["x","y","z"]," return this.a + x + y + z; "]}
string
14
Run Code Online (Sandbox Code Playgroud)
我没有测试较旧的 IE,但它适用于 IE11、FF、Chrome、Edge。
注意,name函数的 丢失了,如果您使用该属性,那么您将无能为力,真的。
您可以将其更改为不易使用prototype,但如果您需要,则可以这样做。
Has*_*own 12
如果您需要一种在 ES6 中序列化箭头函数的方法,我已经编写了一个序列化器,可以让一切正常工作。
您所需要做的就是JSON.stringify()照常调用函数或包含该函数的对象,然后调用Function.deserialise另一端以使魔法发挥作用。
显然你不应该期望闭包能够工作,它毕竟是序列化,但是默认值、解构、、、、成员this函数,它们都会被保留。
如果您只使用 ES5 符号,请使用我的其他答案。这真的是超越了argumentsclass
在 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
Run Code Online (Sandbox Code Playgroud)
最后,魔法
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];
};
Run Code Online (Sandbox Code Playgroud)
function foo() {
alert('native function');
return 'Hello, serialised world!';
}
Run Code Online (Sandbox Code Playgroud)
var storedFunction = foo.toString();
Run Code Online (Sandbox Code Playgroud)
var actualFunction = new Function('return ' + foo.toString())()
Run Code Online (Sandbox Code Playgroud)
foo.toString()将是函数foo的字符串版本
"function foo() { ... return 'Hello, serialised world!';}"
Run Code Online (Sandbox Code Playgroud)
但是new Function取一个函数的主体而不是函数本身.
请参阅MDN:功能
因此,我们可以创建一个函数,将函数返回给我们并将其分配给某个变量.
"return function foo() { ... return 'Hello, serialised world!';}"
Run Code Online (Sandbox Code Playgroud)
所以现在当我们将这个字符串传递给构造函数时,我们得到一个函数,然后我们立即执行它以获取原始函数.:)