A lot of people have asked me over the years how various kinds of event binding work. Basically, event binding works like this:
1) Someone clicks on a button,
2) then a miracle happens, and…
3) the button’s event handlers execute.
It’s that second step that people struggle with.
First, some terminology. I studied applied mathematics, and somethings we talked about quite a bit were sources and sinks. Sources produce something — a faucet produces water at a certain rate, for example. A sink takes that water away. We’ll borrow this terminology for our discussion of events. An event source is something that produces events, like a button or a timer. An event sink is something that consumes events, like an event handler function. (Event sinks are also sometimes called “listeners”, which mixes metaphors somewhat, but that’s hardly unusual in this profession.)
This terminology leads to a rather unfortunate homonymy — when I first heard “this method sinks the click event”, I heard “this method syncs the click event”. When we talk about event sinks, we’re talking about the consumer of something, not about synchronizing two things in time. (Sinks, of course, can be asynchronous…)
The miracle actually isn’t that miraculous. Implementing event sources and sinks requires two things: first, a way to wrap up a function as an object, such that when the source wants to “fire” the event, all it does is invokes the sink’s wrapper. Second, a way for the thread to detect that the button, or whatever, has been pressed and thereby know to trigger the sink wrappers.
An explanation of the magic behind the latter would take us fairly far afield. Suffice to say that in IE, the details of how that mouse press gets translated into windows messages and how those messages are dispatched by the COM message loops behind the scenes are miracles that I don’t want to talk about in this article. I’m more interested in those wrappers.
In the .NET world, an object that can be invoked to call a function is called a delegate. In JScript Classic, all functions are first-class objects, so in a sense, all functions are delegates. How does the source know that the developer wishes a particular delegate (ie, event sink) to be invoked when the event is sourced?
Well, in IE, it’s quite straightforward:
function doSomething() { }
button1.onclick = doSomething; // passes the function object, does not call the function
But here’s an interesting question — what if you want TWO things to happen when an event fires? You can’t say
function doSomething() { }
function doOtherThing() { }
button1.onclick = doSomething;
button1.onclick = doOtherThing;
because that will just replace the old sink with the new one. The DOM only supports “single-cast” delegates, not “multi-cast” delegates. A given event can have no more than one handler in this model.
What to do then? The obvious solution is to simply combine the two.
function doSomething() { }
function doOtherThing() { }
function doEverything() { doSomething(); doOtherThing(); }
button1.onclick = doEverything;
But what if you want to dynamically add new handlers at runtime? I recently saw an inventive, clever, and incredibly horribly awful solution to this problem. Some code has been changed to protect the guilty.
function addDelegate( delegate, statement) {
var source = delegate.toString() ;
var body = source.substring(source.indexOf(‘{‘)+1, source.lastIndexOf(‘}’)) ;
return new Function(body + statement);
}
Now you can do something like this:
function dosomething() { /* whatever */ }
button1.onclick = dosomething;
// … later …
button1.onclick = addDelegate(button1.onclick, “doOtherThing();”) ;
That will then decompile the current delegate, extract the source code, append the new source code, recompile a new delegate using “eval”, and assign the new delegate back.
OK, people, pop quiz. You’ve been reading this blog for a while. What’s wrong with this picture? Put your ideas in comments and I’ll discuss them in my next entry.
This is a gross abuse of the language, particularly considering that this is so easy to solve in a much more elegant way. The way to build multi-cast delegates out of single-cast delegates is to — surprise — build multi-cast delegates out of single cast delegates. Not decompile the single-cast delegate, modify the source code in memory, and then recompile it! There are lots of ways to do this. Here’s one:
function blur1(){whatever}
function blur2(){whatever}
var onBlurMethods = new Array();
function onBlurMultiCast() {
for(var i in onBlurMethods)
onBlurMethods[i]();
}
blah.onBlur = onBlurMultiCast;
onBlurMethods.push(blur1);
onBlurMethods.push(blur2);
I’ll talk about VBScript and JScript .NET issues with event binding another time.
I’m on vacation for the next three weeks, so I might have lots of time for blogging, or lots of other things to do. So if you don’t hear from me, I’ll be back in the new year. Have a festive holiday season!
Comments (11)
You must be logged in to post a comment.
- theCoachThere is a great comic illustrating your intro in Daniel Dennett’s book “Consciousness Explained” [or possibly “Darwin’s Dangerous Idea”. FYI.
- Log in to Reply
- December 12, 2003 at 2:29 pm
- Eric LippertI was making a deliberate reference to this famous cartoon:Is that the one you’re thinking of?
- Log in to Reply
- http://www.sciencecartoonsplus.com/miracle.gif
- December 12, 2003 at 2:38 pm
- Andy Smithjust thought I’d blog my take instead of leaving comments:
http://weblogs.asp.net/asmith/posts/43205.aspx - Log in to Reply
- December 12, 2003 at 3:37 pm
- Tom TrenkaFunny you should mention this topic…I’ve just released the beginnings of an ECMAScript BCL called f(m)…and the centerpiece of this kit is….drumroll…a common Event system that handles this exact problem. Except I basically copied the .NET guys 🙂Log in to Reply
- You can find it at http://fm.dept-z.com, and to be honest, I’d love some feedback. Why did I do it that way? Because I’ve seen a number of examples like the ones you showed above, and it looks SO ugly…
- December 12, 2003 at 4:20 pm
- JayHere’s my solution. It allows the delegating object to behave just like both a method AND an array of delegate methods. I used the “fn.call” syntax to allow contained methods of MulticastDelegate to access its “this” property (i.e., they become methods of the parent object as well).function MulticastDelegate()
{
var $d = function()
{
for( var ix in arguments.callee.delegates )
arguments.callee.delegates[ix].call(this, arguments )
}
$d.delegates = new Array();
$d.push = function(f) { this.delegates.push(f) }
// add other Array methods to taste
return $d;
}Or, if talking about a web page:Log in to Reply - button1.onclick = new MulticastDelegate();
button1.onclick.push( function() {whatever} );
button1.onclick.push( function()
{
a more complex whatever
} ); - example = new MulticastDelegate();
example.push( function() { println( “One” ) } )
example.push( function() { println( “Two” ) } )
example(); - function println(v) { WScript.StdOut.WriteLine(v) }
- December 12, 2003 at 6:47 pm
- JayIf I were really putting this into production, I would also have MulticastDelegate examine it’s “arguments” and push all such elements onto the constructed array. This would allow multiple delegate functions to be added at construction time. Jay.
- Log in to Reply
- December 12, 2003 at 6:50 pm
- Andy SmithAssuming a script running in a browser, there is a MUCH easier way to do all of this. But first i’ll highlight why these kinds of ideas are not always useful. Not everybody writing script that attaches to events, controls all the code on the page. assigning a custom multicast delegate doodad to SomeElement.onclick will be destroyed if some other chunk of code that doesn’t have any clue about your code just comes along and assigns it’s own function to SomeElement.onclick. Note that the code examples here do exactly that. You may be interfering with somebody else’s event handling system.This is why I think it’s better to leverage the browser here, and use addEventListener (for W3C DOM compliant browsers) and/or attachEvent ( for IE). I’ve blogged about this, with my current methodology, here: http://weblogs.asp.net/asmith/posts/30744.aspx
- Log in to Reply
- If you are wondering about a real-world scenario… think about asp.net server controls development. If I package some behavior in a control with script, I want to make sure that my event handling system will not be affected by the page developer or some 3rd party.
- December 12, 2003 at 7:33 pm
- Dan ShappirFirst a nitpick, the code should be:andAnd not as written.blah.onBlur = blur1.andThen(blur2);Another way to approach this problem using BeyondJS is event streams. Streams are objects that behave like containers, only in “time” instead of in “space”. This allows you to use list comprehensions on events:Unsurprisingly this looks a lot like your onBlurMultiCast example, only streams support lots of other list comprehensions such as filer.buttons1.onclick = alert;
button1.onclick = addDelegate(button1.onclick, “doOtherThing();”);A third problem is that the original delegate function looses its scope (closure).Log in to Reply - And yes, for the browser you should simply use addEventListener or attachEvent.
- would break because window.alert.toString() just won’t work. You could try “” + window.alert which would run, but the result still won’t work.
- I can think of various reasons why you would dislike the addDelegate code, primarily the fact that it’s eval. Another problem is that it won’t always work. For example:
- var st = eventStream(blah, “onBlur”);
st.foreach(blur1);
st.foreach(blur2); - andThen is implemented in such a way that you can modify the function chain dynamically. An added advantage, though not in this case, is that the value returned by blur1 is passed as an input arg to blur2.
- Of course, had you been using the BeyondJS library you would have functional composition available, in which case you could simply write:
- > blah.onBlur = onBlurMultiCast;
- > button1.onclick = addDelegate(button1.onclick, “doOtherThing();” );
- December 13, 2003 at 5:52 pm
- David SchontzlerattachEvent has always left something to be desired, but I stopped using it awhile ago so I don’t recall what bugged me about it.
- Log in to Reply
- December 17, 2003 at 4:49 am
- David SchontzlerAndy-Log in to Reply
- Your method still requires people to conform to your function call, which is no different than the other methods. This doesn’t really fix the problem of 3rd party integration you speak of.
- December 17, 2003 at 4:52 am
- Brian R. JamesDo people still attach events that way? attachEvent has been around for years. Why reinvent the wheel?
- Log in to Reply
- December 17, 2003 at 5:10 pm
Follow Us
Popular Tags
C# Scripting JScript VBScript Language Design COM Programming Rarefied Heights Puzzles Rants Performance Security C# 4.0 Non-computer SimpleScript JScript .NET Immutability Code Quality Pages Recursion Books
Archives
- November 2012 (5)
- October 2012 (5)
- September 2012 (2)
- August 2012 (5)
- July 2012 (2)
- All of 2012 (47)
- All of 2011 (66)
- All of 2010 (90)
- All of 2009 (100)
- All of 2008 (43)
- All of 2007 (63)
- All of 2006 (25)
- All of 2005 (97)
- All of 2004 (167)
- All of 2003 (88)
After the “I’m a nine”, I always ask them “what question would you ask someone who was an 8.9 that they would have difficulty answering – just general subject is fine, but a specific question is great”. The real nines have a question like (I normally tech .net) “what should you look out for when casting a structure to an interface and calling methods that modify the data”. The non-nines say “Something about how inheritance works”. If nothing else, it really gives you an idea of what they find hard about what you’re teching them in.
The great part about this is, I can then use the good questions on people later! And, if they have good questions I can learn something, too (after watching PDC speakers and going to ask the experts, I had to re-rate myself. I used to say I liked feeling stupid, but ouch — ego blow. And if you’re a six, I’m not saying what I am) .
With regard the 1-to-10 scale and insufficient data: I think that in this case cultural norms fill in most of the blanks. For example, it’s quit clear the scale isn’t linear, most human scales aren’t. The differences between 2 to 3 to 4 are generally insignificant while the differences between 8 to 9 to 10 are huge.
I think the answer to the question provides more information with regards to that person’s ability to accurately judge his or her own capabilities, the personal honesty to accept this judgment, and the guts to give you the correct answer. Without additional questions, it’s you who doesn’t have sufficient data to evaluate the answer.
I personally have never used this question, but I’ll certainly consider it. A good question I’ve recently learned about is: “which good CS book have you recently read?” Obviously this question is better suited for someone who is not straight out of the University.
A question I might ask somebody answering 9 is: “why is it a bad idea to overload the && operator?”
Indeed, ability to rate one’s knowledge of a subject accurately is strongly correlated with one’s knowledge! Dilletants tend to compare themselves against their peers, and hence produce inaccurate ratings; experts tend to compare themselves against gurus.
As for the && operator — I don’t think I’ve ever actually overloaded an operator other than “new”. It’s not very often that one needs to add two parsers together. I’ve always disliked the entire idea of operator overloading — making << mean “stream to a file” struck me as incredibly disgusting when I first heard of it, and my opinion hasn’t improved since then.
I can think of lots of reasons to avoid overloading in general. Something that strikes me about the && operator is that it is a short-circuiting operator — one might imagine that this would lead to bizareness when overloaded.
> Something that strikes me about the && operator is that it is a short-circuiting operator
Exactly. Say a and b are expressions of type bool. If you write:
c = a && b;
if a is false b isn’t evaluated at all – short circuit.
If a and b are expressions of UDT X with an overloaded && operator then the previous expression translates to a function call:
c = X::operator&&(a, b)
In this case both expressions are evaluated regardless of their values. This discrepancy in behavior is why overloading binary logical operators is a bad idea.
I agree that in most cases operator overloading can detract rather than enhance readability (BTW you can pass this sentiment on to whomever elected to use += with delegates). OTOH where would a string class be without an overloaded + operator?
And on a related issue, another 9 question: what is Koenig lookup?
For more discussions of “weird” C++ stuff check out the thread:
http://lambda.weblogs.com/discuss/msgReader$6697
That question is a little too jargony. Case in point: I once wrote an internal spec describing the insanely complex rules that JScript .NET uses to resolve overloads at compile time and run time. I could certainly describe algorithms for argument-dependent name resolution, why you’d want to use them, and what the practical pitfalls are when using the Reflection layer to do so, but until two minutes ago I didn’t know that this was called “Koenig lookup”! (I’m sure that Herman, who actually implemented that algorithm in JScript .NET, knows that it’s called Koenig lookup, but he’s got a PhD in language design…)
I actually kind of like += for delegates, though it struck me as odd at first. It gets across the idea that you are adding something and at the same time modifying the LHS. However, when designing a new language, one can add new keywords and operators pretty easily. I personally would have created a new operator or syntax rather than overloading += in this weird way.
Next time I see Anders I’ll ask him what motivated that decision. Don’t hold your breath waiting — I haven’t had a meeting with Anders in over six months.
I know Herman was against += as well. JScript’s method of directly calling add_EventName() and remove_EventName() seems kind of klunky, but at least you know exactly what is going on.
Or hey, AddHandler from VB.NET. 🙂
[quote]what happens when a virtual base class destructor calls a virtual method overridden in the derived class?[/quote]
Is a 9-out-of-10 supposed to say, without looking up, something along the lines, “Chapter 12.7 ‘Special member functions – Construction and destruction’, verse 3 of The Holy Standard says, ‘When a virtual function is called directly or indirectly from a constructor or a destructor, and the object to which the call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor’s own class or in one of its bases, but not a function overriding it in a class derived from the constructor or destructor’s class, or overriding it in one of the other base classes of the most derived object (1.8)’. Thus, the virtual base class V destructor V::~V shall invoke the virtual method V::f, not the overridden D::f”?
I personally would not expect the 9-out-of-10 to quote chapter and verse out of the C++ standard (I would probably be somewhat scared if he did). I would expect him to understand the implications. For example, the “indirectly” bit is very important. Misunderstood it can result in application misbehavior that this person would simply not understand, and thus be unable to correct.
By the way, my first reaction was, “Is it even legal, to call a virtual method from a base destructor, when the derived object has already been partially destroyed?” Then, after digging into the standard, it turned out it is. Oops. Anyway, this is a questionable practice.
It’s a terrible practice. But to answer the question, no, I don’t expect 9/10’s to have memorized the specification. I do expect that 9/10’s should be able to say something like:
“I don’t know offhand,but let me see if I can reason it out. The base class destructor runs after the derived class destructor. That means that when the virtual function is called, all the state that makes the derived class different from the base class has been destroyed. In a sense, after the destructor runs, the “this” pointer really isn’t pointing to a valid instance of the derived class anymore! Therefore, I’d expect that a virtual function call would call the base class version, because the derived class version might depend upon now-destructed state. This would be pretty easy to implement — the derived class destructor could, as its final act, change the virtual function pointer to point to the base class vtable instead of the derived class vtable.”
That’s a 9/10 answer. It demonstrates that the candidate understands destructors, virtual functions, how virtual functions are implemented, and most important, that the candidate can think like a language designer. Deciding what the “for” loop syntax looks like is easy. The hard part of language design is figuring out the right thing to do in these weird corner cases.
Sometime I’ll tell you guys about the new data type that I tried to get them to add to VB.NET — a variation of the Variant that I called the “Deviant”. Now _that_ was a host of corner case bugs waiting to happen.
Couple interesting links on related topics (Disclaimer: Not Necessarily Directly Applicable to Your Situation!):
http://www.gladwell.com/2000/2000_05_29_a_interview.htm
http://dabbler.typepad.com/ooze/2003/12/interesting_bit.html