How can I serialize a function in JavaScript?

For example, say I have a function defined as follows:

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

I want to be able to serialize that function and store it using localStorage. How can I go about doing that?


Solution 1:

Most browsers (Chrome, Safari, Firefox, possibly others) return the definition of functions from the .toString() method:

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

Just be careful because native functions won't serialize properly. For example:

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

Solution 2:

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

Serializing

var storedFunction = foo.toString();

Deserializing

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

Explanation

foo.toString() will be string version of the function foo

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

But new Function takes the body of a function and not the function itself.

See MDN: Function

So we can create a function that returns us back this function and assign it to some variable.

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

So now when we pass this string to the constructor we get a function and we immediately execute it to get back our original function. :)

Solution 3:

I made this answer to address some pretty big flaws with the existing answers: .toString()/eval() and new Function() on their own wont work at all if your function uses this or named arguments (function (named, arg) {}), respectively.

Using toJSON() below, all you need to do is call JSON.stringify() as usual on the function, and use Function.deserialise when parse()ing.

The following wont work for concise functions (hello => 'there'), but for standard ES5 fat functions it'll return it as it was defined, closures notwithstanding of course. My other answer will work with all that ES6 goodness.


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

Take a look at the DEMO

At it's simplest:

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

More usefully, you can serialise entire objects containing functions and pull them back out:

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

Outputs:

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

I didn't test older IE's, but it works on IE11, FF, Chrome, Edge.

NB, the name of the function is lost, if you use that property then there's nothing you can do, really.
You can change it to not use prototype easily, but that's for you to do if that's what you need.