I study JavaScript Proxy Pattern, but I still do not get, where I can benefit from it. I would therefore like to provide you with two examples and kindly ask you to point at the difference between them.
Please, take a look at the code below:
- What is the difference between the two
addEventListenercalls? One of them callshandleDropin regular way. The other uses Proxy Pattern. - What will I gain using Proxy pattern approach?
I tested both functions, and they both call handleDrop successfully.
DndUpload.prototype.buildDropZone = function ()
{
var self = this,
this.dropZone.addEventListener('drop', function (e) { self.handleDrop.call(self, e) }, false);
this.dropZone.addEventListener('drop', self.handleDrop, false);
DndUpload.prototype.handleDrop = function (e)
{
alert("test");
...
};
}
You can provide me with good reference which contains very clear explanation of Proxy Pattern in JavaScript.
So what you’re describing in your example isn’t so much a demonstration of the Proxy pattern as much as a demonstration of confusion regarding the “calling object” and how it works in JavaScript.
In JavaScript, functions are “first-class.” This essentially means that functions are data just like any other data. So let’s consider the following situation:
So, we have an object
a, which has two properties:fnandx. We also have variablesx,fn(which is a function returningthis.x), andnothing(which returns whatever it gets passed).If we evaluate
a.x, we get1. If we evaluatex, we get2. Pretty simple, eh? Now, if we evaluatenothing(a.x), then we get1. That’s also very simple. But it’s important to realize that the value1associated with the propertya.xis not in any way connected to the objecta. It exists independently and can be passed around simply as a value.In JavaScript, functions work the same way. Functions that are properties (often called “methods”) can be passed as simple references. However, in doing so, they can become disconnected from their object. This becomes important when you use the
thiskeyword inside a function.The
thiskeyword references the “calling object.” That’s the object that is associated with a function when that function is evaluated. There are three basic ways to set the calling object for a function:a.fn()), the relevant object (in the example,a) is set as the calling object.callorapplyproperties, then you can explicitly set the calling object (we’ll see why this is useful in a second).window).So, back to our code. If we call
a.fn(), it will evaluate as1. That’s expected because thethiskeyword in the function will be set toadue to the use of the dot operator. However, if we call simplyfn(), it will return2because it is referencing thexproperty of the global object (meaning our globalxis used).Now, here’s where things get tricky. What if you called:
nothing(a.fn)()? You might be surprised that the result is2. This is because passinga.fnintonothing()passes a reference tofn, but does not retain the calling object!This is the same concept as what’s going on in your coding example. If your function
handleDropwere to use thethiskeyword, you would find it has a different value depending on which handler form you use. This is because in your second example, you’re passing a reference tohandleDrop, but as with ournothing(a.fn)()example, by the time it gets called, the calling object reference is lost.So let’s add something else to the puzzle:
You’ll note that while
bhas anxproperty (and therefore satisfies the requirements forfn‘s use ofthis), it doesn’t have a property referencingfn. So if we wanted to call thefnfunction with itsthisset tob, it might seem we need to add a new property tob. But instead we can use the aforementionedapplymethod onfnto explicitly setbas the calling object:This can be used to “permanently” bind a calling object to a function by creating a new function “wrapper.” It’s not really permanently binding, it’s just creating a new function that calls the old function with the desired calling object. Such a tool is often written like so:
So after executing that code, we could do the following:
It’s nothing tricky. In fact, the
bind()property is built into ES5 and works pretty much like the simple code above. And ourbindcode is actually a really complicated way to do something that we can do more simply. Sinceahasfnas a property, we can use the dot operator to call it directly. We can skip all the confusing use ofcallandapply. We just need to make sure when the function gets called, it gets called using the dot operator. We can see how to do it above, but in practice, it’s far simpler and more intuitive:Once you have an understanding of how data references can be stored in closure scope, how functions are first-class objects, and how the calling object works, this all becomes very simple to understand and reasonably intuitive.
As for “proxies,” those also exploit the same concepts to hook into functions. So, let’s say that you wanted to count the number of times
a.fngets called. You can do that by inserting a proxy, like so (making use of some concepts from ourbindcode from above):So now, whenever you call
numCalls(), it will return the number of timesa.fn()was called without actually modifying the functionality ofa.fn! which is pretty cool. However, you must keep in mind that you did change the function referenced bya.fn, so looking way back to the beginning of our code, you’ll notice thata.fnis no longer the same asfnand can’t be used interchangeably anymore. But the reasons should now be pretty obvious!I know that was basically a week of JavaScript education in a couple pages of text, but that’s about as simple as it gets. Once you understand the concepts, the functionality, usefulness, and power of many JavaScript patterns become very simple to understand.
Hope that made things clearer!
UPDATE: Thanks to @pimvdb for pointing out my unnecessary use of
[].slice.call(arguments, 0). I have removed it because it’s, well, unnecessary.