Delegates in JS 14

I think we’ve all made the mistake while we are doing OOP in JavaScript with this not pointing to what we expected.

this.obj.onfoo = this.handleFoo;

In the code above this inside handleFoo would point at this.obj and not at this. This is usually worked around with the cumbersome code:

var self = this;
this.obj.onfoo = function (e) {
   return self.handleFoo(e);
};

This can be simplified a lot by simply extending Object with the following:

Object.prototype.createDelegate = function (sMethodName) {
   var self = this;
   return function () {
      return self[sMethodName].apply(self, arguments);
   };
};

or

function createDelegate(oObject, sMethodName) {
   return function () {
      return oObject[sMethodName].apply(oObject, arguments);
   };
}

With the above code we get the following:

this.obj.onfoo =  this.createDelegate("handleFoo");

or

this.obj.onfoo = createDelegate(this, "handleFoo");

A big warning is needed here. Do not do this for DOM/COM objects in IE or you’ll end up with serious memory leaks.

In the Bindows world we went a different route and added an extra parameter to the EventTarget interface to solve the most common case:

this.obj.addEventListener("foo", this.handleFoo, this);
  • http://www.protoscript.net/ protoscript

    That’s funny Erik… I had just extended Object in the same way about two weeks ago. As always, I am jealous of your coding style (and your writing style, esp since English is not your native language!). I really enjoyed State of the Nation, too.

    Hope to speak to you soon, my friend.

  • http://erik.eae.net Erik Arvidsson

    There are of course serious draw backs to extending Object and I would not do it in a real world application because it breaks the usage of Objects as hash tables. Therefore the other alternative is better (making it a static method of Function might make more sense).

    The reason I posted this today was becasue I saw that they were doing something similar in Google maps.

    Object.prototype.eventHandler = function (sMeth) {
    var self = this;
    return function (e) {
    if (!e) e = window.event;
    if (!e.target) e = e.srcElement;
    // I would add preventDefault and stopPropagation as well
    return self[sMeth](e);
    };
    };

  • Dean Edwards

    Good tip Erik. I’ll point out that IE5.0 does not support Function.apply. But many people have stopped supporting this browser (even MS themselves on the new MSN Serach). So maybe this is not an issue. Except that it still has more share than Opera and Safari…

    Keep posting these tips btw. As protoscript says, you have a clear writing and coding style.

  • http://fm.dept-z.com Tom Trenka

    I try to not “tout my own horn”, as it were, but you might want to take a look at the Events system I set up in f(m) (http://fm.dept-z.com/index.asp?get=/Resources/Reference/System/Events is a decent place to start)…it’s modeled directly on the .NET delegate system, and solves this problem by forcing the event handler in question to take 2 arguments instead of one–the object that fired the event, and an event arguments object.

    Nice thing about it is that 1. it can be applied to anything and 2. it preserves the original scope in a way that is very clear.

    Thought you might be interested.

  • http://erik.eae.net Erik Arvidsson

    Tom: I looked at f(m) a week or so back. It is very nice. Yesterday I also saw dojo. Also nice. I’ll try to comment on that at a later time.

    For commercial APIs I recommend using what you are suggesting or doing what we are doing in Bindows.

    Bindows:

    this.obj.addEventLsitener(“foo”, this.handleFoo, this)

    f(m)?

    this.obj.OnFoo.Add(new Events.EventHandler(this.handleFoo));

    The documentation was a bit confusing here. I could not read out how to do it in the right way to get this.handleFoo working as expected. You say that it takes 2 arguments so then I hope it would finally end up something like this:

    this.obj.OnFoo.Add(this.handleFoo, this)

    The good thing with this over the W3C/Bindows model is that the events can be discovered more easily. The bad thing is that custom events require an interface change.

  • http://fm.dept-z.com Tom Trenka

    Hmm. It looks like my quick docs suck then :)

    Here’s the deal: *your* function takes two args. So:

    function myHandler(src, ea) {
    // do something
    }

    this.obj.OnFoo.Add(new Events.EventHandler(myHandler))

    …like that. I suppose I should really go through and make that clearer…

  • http://erik.eae.net Erik Arvidsson

    Tom: Isn’t the src argument same as event.target (in DOM)?

    a.b.OnFoo.Add(new Events.EventHandler(a.handleFoo))

    How would I get to a in a.handleFoo (when called from the event handler)? I get the impression that src would point to a.b? At least that is how .NET does it.

  • http://fm.dept-z.com Tom Trenka

    If you’re attaching the event handler to a DOM object, yes, it is the equivilent of target, absolutely.

    The difference is that the event handler is executed in the scope of “a” in your example, *not* in the scope of “b”; in this particular case, the src arg of your event handler *should* be a reference to “b”. So yes, it works very much like the way .NET does (which was the point, a lot of the aim of f(m) is to bring some of the cleanliness of the .NET framework to JS).

    The big difference with what I’ve done is that it’s a unified model; it works the same whether or not you are firing custom events on your own objects, or you’ve attached them to DOM objects. Which is pretty nice. I was kind of impressed with myself when I was able to pull something like this:

    document.getElementById(“myFoo”).onclick = (new Events.Listener()).Invoke ;
    document.getElementById(“myFoo”).onclick.Add(new Events.MouseEventHandler(myHandlerFunc)) ;
    :)

  • http://fm.dept-z.com Tom Trenka

    Sorry, quick follow up.

    In your example:
    a.b.OnFoo.Add(new Events.EventHandler(a.handleFoo))

    Supposing this is the actual method:

    a.handleFoo = function(src, ea) {
    this.something = “something”; // this would be on “a”
    src.something = “somethingElse” ; // this would be on “b”
    }

    Make sense? By passing the object that fired the event as the first arg to the handler function, I’m able (through some trickery in Events.Listener.Invoke) to preserve the scope of the original handler function, while passing the object that fired the event.

    Honestly, for me it’s made writing event-based code a lot simpler; I don’t have to worry about the problem that you posted about at all now, since I know that “this” will always refer to the lexical scope that I defined in the first place.

    (It’s for things like this that I love Javascript, btw)

  • http://erik.eae.net Erik Arvidsson

    The onliclick.Add feature is very nice.

    I’m still not sure how you solve it because to me it seems you are solving an equation with 2 unknowns.

    var tmp = a.handleFoo;
    a.b.OnFoo = (new Events.Listener).Invoke;
    a.b.OnFoo.Add(new Events.EventHandler(tmp));

    I’m missing the in param that passes a to the event handler… I’m off to look at the f(m) code to see if it can explain this.

  • Tom Trenka

    Yeah, it’s a bit confusing. The basic deal is that Events.EventHandler wraps the original function (the one arg you pass it), and hangs onto a reference to it, as well as defines what EventArgs object is passed to it.

    Then in the Listener.Invoke code, the internal array of EventHandlers is executed. Here’s the code:

    this.Invoke = function(e) {
    	var t = [] ;
    	for (var i = 0; i < handlers.length; i++) t.push(handlers[i]) ;
    	while (t.length > 0) {
    		var h = t[t.length - 1] ;
    		(h.GetHandler())(this, new (h.GetArgsClass())(e)) ;	// invoke the event handler
    		if (h.RunOnce) me.Remove(h) ;
    		t.pop() ;
    	}
    } ;
    

    Ignoring the h.RunOnce stuff, basically what’s happening is the following:
    1. the context of execution is the target object (the scope issue you mentioned in this post).
    2. The EventHandler objects in the handlers array wrap the original reference, and executes it directly (as opposed to in any particular scope).

    Here’s the money line:

    (h.GetHandler())(this, new (h.GetArgsClass())(e)) ;

    h.GetHandler() returns a reference to your original function, without changing the execution scope.

    h.GetArgsClass() returns a new instance of the particular EventArgs object you’ve defined for that EventHandler.

    “this” is passed as the first arg to your function.

    It’s pretty confusing to figure out how it works, but it does work pretty well. Of course, if you figure out a situation where it doesn’t work as advertised, I’m all ears.

  • Tom Trenka

    Dammit. I keep posting an explanation without putting an example up…taking the functions from your last one, the money line would look like this:

    (a.handleFoo)(a.b, new EventArgs(e)) ;

    That’s basically how it’s executed. Does that help explain the second unknown at all?

  • http://-- Simonboris

    For those who refuse to use the Function.apply because old browser do not support it, copy paste this in you js files.

    If Function.apply is not available in a browser, this will be run and it do the smae thing:

    if(!Function.prototype.apply)
    {
    Function.prototype.apply = function(o,a)
    {
    var r, e;
    if(!o) var o = window;
    if(!a) var a = new Array();
    var b = new Array();
    for(var i=0;i

  • http://-- Simonboris

    if(!Function.prototype.apply)
    {
    Function.prototype.apply = function(o,a)
    {
    var r, e;
    if(!o) var o = window;
    if(!a) var a = new Array();
    var b = new Array();
    for(var i=0;i