Efficient closures in JavaScript
I like C++ and JavaScript, and enjoy writing software in either language. Regardless, today I just want to discuss features I miss from C++ when using closures in JavaScript.
Closures typically marry an execution environment with a body of statements. Variables not defined within the scope of the closure, free variables, are pulled from the surrounding context and remembered in some manner until the closure is executed. Closures allow programmers to build functions that can be executed in a context separate from their definition.
var a = 1;
var f = function (b) { return a + b; };
f(2); // returns 3
In JavaScript, closures capture their free variables by reference, so the behavior of a closure can change after it has been constructed if its free variables change.
var a = 1;
var f = function (b) { return a + b; };
a = 2;
f(2); // returns 4 now
However, JavaScript does not separate the definition of a closure's environment from its body. One consequence is that creating two closures with identical operations working on separate data requires creating what could essentially be two copies of the same body.
C++ takes a different approach. Closures are objects of a class with a function call operator:
struct Closure {
int a;
Closure(int a) : a(a) {}
int operator() (int b) const { return a + b; }
};
auto f = Closure(1);
f(2); // returns 3
auto g = Closure(2);
g(2); // returns 4
// f and g must each be large enough only to hold an int
All closures constructed from the class share the same body but carry different environments.
It is my assumption that constructing a closure in JavaScript is significantly more expensive than constructing a simple object. Savings could be had by adopting the approach from C++.
Why do I care about any of this? In HotDrink, we wrap variables in proxies to
encapsulate library behavior while shielding programmers from excessive
syntactic requirements. Variables are constructed with various helpers:
variable
, computed
, list
, etc. Ideally, these helpers would return a
value that could be read and assigned like any other:
var x = hd.variable();
x = 2;
var y = x * x; // y === 4
Unfortunately, HotDrink needs to intervene during each read and write of a variable, so we must return a proxy. Using a closure imposes the smallest syntactic burden:
var x = hd.variable();
x(2);
var y = x() * x(); // y === 4
Curiously enough, C++ offers another advantage here. Reads and writes of a variable can be interrupted by defining a type-conversion operator and assignment operator, respectively, for its type:
struct Proxy {
int val;
Proxy() : val(0) {}
Proxy& operator=(int rhs) { val = rhs; return *this; }
operator int() const { return val; }
};
auto x = Proxy();
x = 2;
auto y = x * x; // y == 4
Regardless, my primary concern is the cost of constructing potentially hundreds of proxies that could share bodies but don't. My suspicion is that rampant closures are the biggest offenders in page load.
We do have the option of using objects instead of closures for our proxies. Shared bodies could be stored as a method on a common prototype. However, reads and writes would look much nastier, and I think the syntactic penalty is not worth the performance advantage:
var x = hd.variable();
x.write(2);
var y = x.read() * x.read();