bio photo

JavaScript Proxies - "Leaky this"

You can introduce AOP interception patterns by either changing the original instance or by using additional proxy objects. The later can introduce confusion among instances if not treated correctly. Skip to “Leaky this” if you’re already familiar with proxies in ECMAScript Harmony.

Rewriting vs. Wrapping

Intercepting normal program flows using aspect oriented and other meta-programming patterns can be used for a variety of advanced tricks. Object-relational mapping, logging, security, customized APIs etc.

You can do this by rewriting the methods of the original class/prototype/instance, effectively providing a single instance. You can also do this by providing wrapper objects. That way you have two or more instances.

Rewriting instances or prototypes is easy to do in JS by overriding the functions on existing objects with your own wrapper functions. Although it’s not always possible on the magical host objects. It can also be a problem when these objects have different requirements in various contexts. Such as in a secured sandbox vs. host environment.

Wrappers allow you to leave the original object intact while providing the wrapper to a specific context as if it’s the real object.

Wrappers in ECMAScript 5

In ECMAScript 5 you can already do seemingly perfect wrappers by getting all the properties of an object. Then you define those properties on the wrapper - called a proxy. These are custom getters and setters that delegate to the original object - called the subject.

var subject = new Node();

var proxy = Object.create();

Object.getOwnPropertyNames(subject).forEach(function(name){
	Object.defineProperty(proxy, name, {
		get: function(name){
			// interception code
			return subject[name];
		}
	}
});

(For simplicity I’ve left out the prototype, setters, enumerations rules etc. Getters are enough to illustrate my point.)

These are fine for frozen objects. The problem is that they don’t represent later changes to the subject. E.g. adding or deleting properties. Additionally, property changes applied to the proxy aren’t propagated back into the subject.

Proxies in ECMAScript Harmony

It looks like the next version of ECMAScript will get support for proxy object that can delegate all operations dynamically - using so called “catch-all” functions.

var subject = new Node();

var proxy = Proxy.create({
	get: function(receiver, name){
		// interception code
		return subject[name];
	}
});

When we’re reading a property from the proxy instance, our get-function gets called with the receiver (the proxy instance itself) and the name of the property that should be loaded. We delegate this request to the subject by reading the same property. We can insert any intercepting code as we choose.

This is more efficient and allows for dynamic evaluation of intercepting code. Many ORM patterns - such as Active Record - often use this technique in dynamic environments.

“Leaky this”

However, a problem occurs when these two instances get confused. Imagine this scenario:

var Node = function(){
  var self = this, children = [];
  self.addChild = function(child){
	children.push(child);
	child.parent = self;
	return self;
  };
};

If we wrap an instance of Node in a proxy. Then call:

proxy.addChild(child).addChild(childOther);

We first call addChild through the proxy instance. Our child.parent property is set to the subject instance. The subject instance is returned and we next call addChild directly on the subject instance. Bypassing the proxy.

This is a well-known problem in languages that strongly binds the “this” keyword to a specific instance. Roger Alsing calls this “leaky this” which I find to be an appropriate term since the abstraction is leaking.

However, imagine this JavaScript code:

var Node = function(){
  this.children = [];
};

Node.prototype.addChild = function(child){
  this.children.push(child);
  child.parent = this;
  return this;
};

The addChild function now references the “this” keyword. If we repeat the same call in this scenario we get a different result. We call addChild while passing the proxy as the “this” reference. We set the parent property of child to the proxy instance. The proxy instance is returned, and the process is repeated on the second call. This shows the beauty of not having the “this” keyword bound to specific instances.

Although, sometimes you don’t want to use the proxy instance in internal functions. You could use this hybrid to use the subject for whatever use internally, while exposing any proxies externally:

var Node = function(){
  var self = this, children = [];
  self.addChild = function(child){
	children.push(child);
	child.parent = self;
	return this;
  };
};

Conclusion

Brendan Eich points out that unaware consumers of a proxy should be able to treat it just as any other object. This is only true if the provider of both the proxy and the subject have carefully considered the implications of two instances and made sure that no leaking occurs. Code that can be wrapped needs to carefully consider what instance it is currently working with.

Not all code can be wrapped using a proxy indiscriminately.