JavaScript Proxies – “Leaky this”
Thursday, November 18th, 2010 by Sebastian MarkbågeYou 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.
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.
Tags: JavaScript, Leaky Abstraction, Leaky This, Proxies, Wrapper
November 18th, 2010 at 02:13
Proxies are not useful only for wrappers, but you are quite right that when wrapping one must take care not to leak the unwrapped object, through any hazard (|this| is just one in JS, alas).
Firefox uses Proxies internally for its security wrappers, as I mentioned in my blog post, but do implement this as a non-leaky membrane we also use a reference monitor on the ouside of same-origin “compartments”, and a weak map to preserve wrapper/wrappee identity relations. So again your point is well-taken.
There is a school of thought, Doug Crockford and Mark Miller are exponents of it, that says “don’t use |this|, use lexical scope and nothing else”. This style of JS programming obviously avoids leaky-this.
/be
November 18th, 2010 at 03:30
I think it’s too general to say “don’t use |this|” since that can cause leaks in certain cases as my examples show. Additionally, it can (obviously) negatively affect performance where you have many instances with many methods – compared to the prototypical model. Each method will need it’s own closure for each instance.
Thanks for providing your view on this, Brendan. Proxies are indeed awesome for many uses.
November 18th, 2010 at 03:40
To your point about weak maps. I’ve used that pattern myself to implement DOM-like wrappers. To continue my example I can have the proxy cast any wrappee output as the wrapper:
return ToWrappedNode(subject.addChild(child));
That way return values never expose unwrapped subjects. However, this still doesn’t solve the problem of subject code calling into external code with itself as an argument. E.g. double dispatch.
var Node = function(external){
var self = this;
self.doWork = function(){
external.doSomeWork(self);
};
};
Just using pure lexical scope can still be a problem.
November 18th, 2010 at 06:11
Agree that “don’t use |this” is not general enough. See my last comment (comment 7) in my blog, though. Slides 74-78 in my talk show a membraning system that avoids |this| leaks.This handles the double-dispatch case in your last comment. At some cost, of course, but no free lunch.
/be
November 18th, 2010 at 07:18
Sorry, I’m wrong — deep wrapping via membranes does not solve the external.doSomeWork(self) leak in your last comment — not without putting a membrane around the doWork function or the Node constructor. One area of blindness even for proxies: the upvars captured in a closure. Thanks,
/be
November 18th, 2010 at 07:32
Right. If the “external” variable was set after it had been wrapped in a membrane it would work. That probably solves most real scenarios.
The same problem occurs if “external” is a property on the subject (self). So it’s not limited to upvars directly but indirectly through “self”.
November 18th, 2010 at 08:27
The trick with membranes is to wrap a “root” object such as self, or anything purveyed by a constructor by wrapping the ctor, early enough to wrap all the indirectly reachable stuff (transitive closure, wrapped only on demand). Membranes work well that way, but (your point) if the cat is out of the bag, then it’s too late.
/be
November 19th, 2010 at 15:39
Great article. Another name for the “leaky this” problem coined by Patrick Eugster is the “two body” problem (the fact that proxy and wrapped object have distinct identities). In his paper on “Uniform proxies for Java” he cites it as a problem inherent to the proxy approach. Mark Miller and I acknowledge it in our paper on Javascript proxies, and present membranes as one way to address it. But as both Brendan and you pointed out: membranes only help if you wrap the appropriate initial objects.
December 10th, 2010 at 17:10
Your net web page came up in my research and I’m prompted by what you may have composed on this theme. I am presently branching out my enquiry and thus can not contribute additional, however, I’ve bookmarked your site and shall be returning to maintain up with any future updates. Just like it and thanks for allowing my remark.
February 21st, 2011 at 19:13
It seems I needed to make my own Proxy for E4. I was discussing my work on IRC when someone mentioned a native Proxy object… After some Googling I found your page, and I guess this is what they meant.
However, I don’t see a Proxy object in FireBug currently. I’ll read up more on E5 and how to enable it on a web page – it doesn’t seem to be the default environment.
Just pointing out that this type of object – irregardless of it’s name – is needed in the non-E5 world. My website link points to my project (of the same name), which might be that answer. Comments and critiques welcome, and I do hope more posts will be made on this subject. I’ve found Proxies to be quite necessary in a world of closured/private libraries.
March 28th, 2011 at 15:26
I feel that is an interesting position, it made me feel a bit. Thanks for sparking my contemplating cap. From time to time I get so significantly inside a rut that I just feel like a document.
April 13th, 2011 at 00:50
It sounds like you are doing problems yourself by trying to find to solve this dilemma rather than looking at why their can be a difficulty in the First place. thanks !!! really very helpful post!
April 23rd, 2011 at 09:34
I am very happy to read this. This is the type of manual that needs to be given and not the accidental misinformation that’s at the other blogs. Appreciate your sharing this best doc.
May 20th, 2011 at 01:08
I’m impressed, I must say. Actually rarely do I encounter a site that’s educative and also enjoyable, and I want to tell you, you may have strike the nail on the head. Your concept is excellent; the difficulty is something that not enough individuals are discussing wisely about. I’m really satisfied that I stumbled throughout this in my seek for something talking about this.
May 29th, 2011 at 16:59
I am thankful that I discovered this web site, precisely the right information that I was looking for! .
April 1st, 2012 at 15:00
Interesting article, I will definitaly mark this blog as one of my favorites.
May 20th, 2012 at 06:24
I have recently started a web site, the info you offer on this site has helped me greatly. Thanks for all of your time & work. “The inner fire is the most important thing mankind possesses.” by Edith Sodergran.
May 30th, 2012 at 04:02
Hi Michelle Just curious what you mean by A quick skim shows that at least a dozen law firms are “investigating” the deal’ … how do you identify this?
June 25th, 2012 at 15:52
It is the best time to make some plans for the future and it is time to be happy. I’ve learn this post and if I could I wish to suggest you few interesting things or suggestions. Maybe you could write next articles referring to this article. I desire to read more issues approximately it!
July 12th, 2012 at 13:19
Thanks a lot for sharing this with all people you actually realize what you are speaking approximately! Bookmarked. Please additionally visit my web site =). We will have a link trade arrangement between us
September 26th, 2012 at 23:04
Thanks for all your labor on this web page. My mum really loves carrying out investigation and it’s obvious why. Many of us notice all about the compelling medium you deliver precious tips and tricks via the website and as well invigorate participation from visitors on the area while our own child has always been discovering a whole lot. Take advantage of the rest of the year. You’re conducting a good job.
January 30th, 2013 at 21:45
you will have an excellent blog right here! would you prefer to make some invite posts on my weblog?
April 20th, 2013 at 05:00
For most recent information you have to pay a visit world wide web and
on web I found this site as a finest website for most recent updates.
May 7th, 2013 at 02:57
Great website you have here but I was curious about if you knew of any forums
that cover the same topics discussed here? I’d really like to be a part of online community where I can get comments from other knowledgeable individuals that share the same interest. If you have any suggestions, please let me know. Many thanks!
May 7th, 2013 at 03:09
My spouse and I absolutely love your blog and find
almost all of your post’s to be what precisely I’m looking for.
can you offer guest writers to write content in your case?
I wouldn’t mind producing a post or elaborating on a lot of the subjects you write related to here. Again, awesome website!
May 10th, 2013 at 23:33
Hello there, You’ve done an incredible job. I’ll certainly digg it and for my part recommend to my friends. I am sure they will be benefited from this website.
May 21st, 2013 at 01:59
This paragraph gives clear idea in favor of the new users
of blogging, that truly how to do running a blog.