Serializing Javascript Closures

Posted in Uncategorized by Mike Stay on 2016 January 21

In Javascript, the eval function lets you dynamically look up variables in scope, so you can do a terrible hack like this to sort-of serialize a closure if you’re in the same scope as the variables being closed over:

Given StringMap.js and atLeastFreeVarNames.js from SES, one can define the following:

var s = function(f) {
  // Find free vars in f. (This depends, of course, on
  // Function.prototype.toString being unchanged.)
  var code = f.toString();
  var free = ses.atLeastFreeVarNames(code);
  // Construct code that evaluates to an environment object.
  var env = ["({"];
  for (var i = 0, len = free.length; i < len; ++i) {
    env.push('":(function(){try{return eval("(');
    env.push(')")}catch(_){return {}}})()');
  return "({code:" + JSON.stringify(code) + ",env:" + env.join("") + "})";

// See or
// for versions of stringify that handle cycles.
var t = function(x) { return '(' + JSON.stringify(x) + ')'; }

Then you can use these definitions to serialize inline definitions that only close over “stringifiable” objects or objects behind cut points (see deserialization below):

var baz = {x: 1};
var serializedClosure = t(eval(s(
  function bar(foo) { console.log('hi'); return ++(baz.x)+foo /*fiddle*/; }
// serializedClosure === '({"code":"function bar(foo) { console.log('hi'); return ++(baz.x)+foo /*fiddle*/; }","env":{"function":{},"bar":{},"foo":{},"console":{},"log":{},"hi":{},"return":{},"baz":{"x":1},"x":{},"fiddle":{}}})'

The string serializedClosure can then be stashed somewhere.  When it’s time to deserialize, do the following:

var d = function(closure, localBindings) {
  localBindings = localBindings || {};
  return function() {
    with(closure.env) {
      with (localBindings) {
        return eval("(" + closure.code + ")").apply(this, arguments);
var closure = eval(serializedClosure);
// Hook up local values if you want them.
var deserializedFn = d(closure, {console: console});
// Prints "2foo" to the console.
// Prints "3bar" to the console.

If you want to store the updated state, just re-stringify the closure:

var newSerializedClosure = t(closure);
// newSerializedClosure === '({"code":"function bar(foo) { console.log('hi'); return ++(baz.x)+foo /*fiddle*/; }","env":{"function":{},"bar":{},"foo":{},"console":{},"log":{},"hi":{},"return":{},"baz":{"x":3},"x":{},"fiddle":{}}})'
// Note that baz.x is now 3.

As I said, a very ugly hack, but still might be useful somewhere.