In the last post I demonstrated how you can fiddle with the this reference during a method invocation using Function.call and Function.apply. After reading that post, you should understand why Function.apply was the most interesting method to us. It's because we can use apply() to specify the this reference for an arbitrary target method, and also pass-through an argument list to that target method via the arguments identifier.
With the above understanding in place, let's look at the following code.
How do we get the onclick event to invoke our showMessage() method using the correct this reference? We've already seen this approach doesn't work:
Inside of showMessage(), the this reference will point to the submit button instead of a MagicButtonManager instance, and this._message will be undefined. We've assigned a function object to the onclick event. The button does not invoke that function through an instance of the MagicButtonManger (which would correctly setup the this reference) - it just directly invokes that function.
What we need to do is trick the button into calling showMessage via our instance of the MagicButtonManager, right? As in: manager.showMessage();. Unfortunately, the only thing the event handler knows how to do is invoke a function. It doesn't know how, given an object, to "dot operate" (did I just invent a verb?) and invoke a specific method on that object. Here then, is what we know so far:
What our createDelegate function was doing in a previous post (and what the ASP.NET AJAX Function.createDelegate method essentially does), is this:
Here we are creating a new function object, a shim if you will. When this shim is invoked, it will use apply() on the incoming method parameter, which invokes that specified method. The shim will pass object as the this reference for apply to setup, and the shim does a "pass-through" of all its incoming arguments using the arguments identifier.
The createDelegate function returns a shim as a result. We can assign the return value of createDelegate to an event, so the event can invoke the shim function. When the event fires and shim executes, our this reference is correct thanks to the magic of apply.
Everything works now. Thank you, and goodnight.
What's that? Something doesn't make sense?
We've covered apply and arguments in great detail, so I assume if you are sticking around you might be puzzled by the shim function. How does shim remember the method and object parameters? Since method and object are arguments to createDelegate - don't they go out of scope when createDelegate exits on the closing curly brace? Aren't those function arguments blown away with the crackling pop of a stack frame? Wouldn't those arguments be long gone by the time the button event executes the shim function?
Hang on for one more post...
In order to explain how the createDelegate function works in the last post, we have to understand JavaScript closures and Function.apply(). The apply() method is the easiest subject to tackle, so we'll start there and work up.
Every function in JavaScript has a number of attached methods, including toString(), call(), and apply(). If it sounds odd to you that a function might have its own methods - then remember that every function in JavaScript is an object. Read this article for a refresher. You might also wonder what the difference is between a function and a method. I believe the descriptors 'function' and 'method' are just a JavaScript convention. Functions stand on their own (there is an alert() function, for example), while methods are functions inside an object's dictionary, and we invoke them through the object reference. Every JavaScript object has a toString() method, for example, and we can use the toString() method on a function object to see its source code:
function foo() { alert('x'); } alert(foo.toString());
Because functions are objects they can have their own properties and methods, and we can treat them like data. "Functions as data" is important to remember for the next post, too, but for now we'll focus on two of a function's methods: apply(), and its counterpart: call().
Let's start with the following code:
var x = 10; function f() { alert(this.x); } f();
Here we have a global function by the name of f(). f() uses the this keyword to reference x, but notice we don't invoke the function through an instance of an object. So what object does this reference? this will reference the global object. The global object is where we defined the variable x. The above code does work and will show the value 10 in a dialog.
Both call() and apply() are methods we can use to assign the this pointer for the duration of a method invocation. As an example, here is how we could use the call() method:
var x = 10; var o = { x: 15 }; function f() { alert(this.x); } f(); f.call(o);
The first invocation of f() will display the value of 10, because this references the global object. The second invocation (via the call method) however, will display the value 15. 15 is the value of the x property inside object o. The call() method invokes the function and uses its first parameter as the this pointer inside the body of the function. In other words - we've told the runtime what object to reference as this while executing inside of function f().
Fiddling with the this pointer might sound funny, even perverse, to C++, Java, and C# programmers. What's next? Dogs sleeping with cats? Working nVidia drivers for Windows Vista? It's all part of the fun that is ECMAScript.
We can also pass arguments to the target function via call():
var x = 10; var o = { x: 15 }; function f(message) { alert(message); alert(this.x); } f("invoking f"); f.call(o, "invoking f via call");
The apply() method is identical to call(), except apply() requires an array as the second parameter. The array represents the arguments for the target method.
var x = 10; var o = { x: 15 }; function f(message) { alert(message); alert(this.x); } f("invoking f"); f.apply(o, ["invoking f through apply"]);
The apply() method is useful because we can build a function like createDelegate (from the last post) that doesn't care about the signature of the target method. The function can use apply() to pass all additional arguments to the target method via an array. Are we getting close to a curry function?
var o = { x: 15 }; function f1(message1) { alert(message1 + this.x); } function f2(message1, message2) { alert(message1 + (this.x * this.x) + message2); } function g(object, func, args) { func.apply(object, args); } g(o, f1, ["the value of x = "]); g(o, f2, ["the value of x squared = ", ". Wow!"]);
The problem here is the awkward syntax. We are forcing the caller to stuff arguments into an array just so we call apply(). Fortunately, there is a way to make the syntax easier, but we have to introduce one more topic: the arguments identifier.
In JavaScript, every function essentially has a variable length argument list. The means we can pass 5 parameters to a function even if the function only uses one argument. The following runs without error and displays "H":
function f(message) { alert(message); } f("H", "e", "l", "l", "o");
If we did want to access the other arguments from inside f(), we can use the arguments keyword. arguments references an Arguments object, which has a length property and feels like an array.
function f(message) { // message param is the same as arguments[0] for(var i = 1; i < arguments.length; i++) { message += arguments[i]; } alert(message); } // this will say "Hello" f("H", "e", "l", "l", "o");
Just so you know, arguments is technically not an array, even if it walks and talks like one. arguments has a length property but no split, push, or pop methods. What we can do with arguments inside our previous g() function is copy the incoming arguments after arguments[1] into an array object that we pass to apply.
var o = { x: 15 }; function f(message1, message2) { alert(message1 + (this.x * this.x) + message2); } function g(object, func) { // arguments[0] == object // arguments[1] == func var args = []; // empty array // copy all other arguments we want to "pass through" for(var i = 2; i < arguments.length; i++) { args.push(arguments[i]); } func.apply(object, args); } g(o, f, "The value of x squared = ", ". Wow!");
When we invoke g(), we can pass additional arguments as parameters instead of stuffing the arguments into an array.
At this point, we have the theoretical knowledge needed to understand call and apply, but perhaps you are already asking a question: what if I don't want to immediately invoke the target function f()? What if I just want to arrange all the players in this little drama so that I can invoke f() at some later point (as an event handler, for example), but still have this referencing the desired object (whithout tracking the desired object myself). In an upcoming post, we'll see how to combine our Function.apply and arguments knowledge with the concept of nested functions and closures to answer this very question.
One problem in WWWTC # 16 is the assumptions Estelle makes about this. In C# you don’t need to explicitly use the this keyword to reference an instance field from inside an instance method. It’s tempting to write a method like Estelle’s …
… only to find out the method tries to access a global variable named _input and a global variable named _initialValue. In ECMAScript we need to explicitly bring this into play to reference entries in the object’s own dictionary:
Thanks to delegate inference in C#, it’s also tempting to think we can assign an instance method to a DOM event …
… only to find out we’ve simply given the DOM event a reference to a function object. The DOM element will happily invoke the function directly and without using an instance of an InputManager object. Our this reference inside of the resetToInitalValue function is then useless. Similar code in C# will work, because the C# compiler will magically create a delegate behind the scenes, and that delegate will manage an object reference, and invoke an instance method through that object reference.
There are mechanisms in ASP.NET AJAX to fix the above scenario, but in the end it boils down to giving the DOM element a function object that can remember the proper this reference. A simple approach would look like:
Here we are using the magic of a JavaScript closure to create and return a new function object from inside createDelegate. The DOM event will reference this new function object. When this new function object is invoked by the DOM element, the new function sets up a proper invocation of resetToInitialValue by using Function.apply and passing through the InputManager reference to use as the this reference.
We’ll explain this topic of closures and Function.apply more thoroughly in the future.
Those fixes should get the code working for now, but what else in that code was a monster in the closet?
For one, the event handler registration could use some work. There are tons of JavaScript code and articles written on the topic of how to do safe, non-desctuctive, cross-browser event registrations, so for now I’ll just say if you are using something like ASP.NET AJAX’s $addHandler technique your code will improve over the above approach.
Secondly, it’s good practice to clean up event registrations. The code sets up a scenario where each InputManager holds a reference to DOM element (in the _input field), and that same DOM input holds a reference back to it’s InputManager (via the function object / event handler that formed a closure over the InputManager instance). In the past, these circular references created memory leaks and were a common “memory leak pattern” in Internet Explorer. I say “in the past” because these problems are fixed in IE7 (and in IE6 (with an update)). Still, the generally accepted practice in today’s AJAX patterns is to clean up afterwards, which feels like the right thing to do as not all browsers are perfect in figuring out these circular references.
It’s been some time since the last WWTC, but Estelle Hertz is still cranking out software. This time Estelle was asked to write some JavaScript code for a web page with hundreds of textbox input controls (the page collects details for a life insurance policy). When a user double clicks on any textbox on the page, the textbox should reset itself to its initial value (the value in the control when the page first loaded).
Estelle has yet to experience all the slippery pitfalls in JavaScript development, and writes the following code:
Anyone spot a problem? Two problems? Three problems? Could there be possibly be more than three problems in the 20 line hunk of script?
Look! Actual ASP.NET content is back!
The documentation for WebConfigurationManager.OpenWebConfiguration says:
"Opens the Web-application configuration file as a Configuration object using the specified virtual path to allow read or write operations."
This might lead you to think you'll only find stuff from a single web.config file in the returned Configuration object - but this isn't true. The following code will return the extensions and paths mapped to the web forms PageHandlerFactory for "MyWebSite".
... and you should at least get a result of ".aspx". This ".aspx" entry probably isn't in the web.config for "MyWebSite", but it is in the machine level web.config file (found in %Windows%\Microsoft.NET\Framework\v2.0.50727\CONFIG) by default.
Remember .NET configuration files are hierarchical, and if you look at the documentation for System.Configuration you'll find (emphasis mine):
The Configuration class instance represents the merged view of the configuration settings that apply to a specific physical entity, such as a computer, or to a logical entity, such as an application, or a Web site.
It's common behavior in the technology arena to hold on and wait for the next big thing. Sometimes we wait for bug fixes. Sometimes we wait for new features. It's not just software – I've waited for phones and TVs because I know the next generation will be so much better.
Have you ever picked up a piece of hardware or software and said, "this is perfect - I can't imagine anyone ever making this better?"
If so - how long did that feeling last?
I've been thinking about getting a MySpace page, but first I need a good design. What do y'all think of this?
K. Scott Allen The K stands for Kewl My Interests
C# Other Kewl People
Omniscient Eyes Turn Ons
Semicolons |
Pics of Me, Me, Me, Me, and Me
|
||||||
|
K. Scott Allen Is In Your Extended Network! |
Define: Web 2.0. It's about me, me, and me.