reperiendi

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('"');
    env.push(free[i]);
    env.push('":(function(){try{return eval("(');
    env.push(free[i]);
    env.push(')")}catch(_){return {}}})()');
    env.push(',');
  }
  env.pop();
  env.push("})");
  return "({code:" + JSON.stringify(code) + ",env:" + env.join("") + "})";
};

// See https://gist.github.com/Hoff97/9842228 or
// http://jsfiddle.net/7UYd4/1/
// 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});
deserializedFn("foo");
// Prints "2foo" to the console.
deserializedFn("bar");
// 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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: