www.digital-web.com/articles/scope_in_javascript
Scope is one of the foundational aspects of the JavaScript language, and probably the one I've struggled with the most when building complex programs. I can't count the number of times I've lost track of what the this keyword refers to after passing control around from function to function, and I've often found myself contorting my code in all sorts of confusing ways, trying to retain some semblance of sanity in my understanding of which variables were accessible where. This article will tackle the problem head-on, outlining definitions of context and scope, examining two JavaScript methods that allow us to manipulate context, and concluding with a deep dive into an effective solution to ninety percent of the problems I've run into. Every bit of your JavaScript program is executed in one execution context or another. You can think of these contexts as your code's neighborhood, giving each line an understanding of where it comes from, and who its friends and neighbors are. As it turns out, this is important information, as JavaScript societies have fairly strict rules about who can associate with whom; execution contexts are better thought of as gated communities than as open subdivisions. We can refer to these social boundaries generally as scope, and they're important enough to be codified in each neighborhood's charter, which we'll refer to as the context's scope chain. Code within a particular neighborhood can only access variables listed on its scope chain, and prefers interaction with locals to associations outside its neighborhood. Practically speaking, evaluating a function establishes a distinct execution context that appends its local scope to the scope chain it was defined within. JavaScript resolves identifiers within a particular context by climbing up the scope chain, moving locally to globally.
Duke professor, even though the latter two are (arguably) better known. Let's walk through some example code to explore the implications: <script type="text/javascript"> var ima_celebrity = "Everyone can see me! She's politically active, talking with the_president on a fairly frequent basis, and incredibly friendly; she'll sign autographs and answer questions for anyone she runs into. That said, she doesn't have a whole lot of personal contact with her fans. She's pretty sure they exist and that they probably have lives of their own somewhere, but she certainly doesn't know what they're doing, or even their names. She's always walking the streets of her town, chatting up her constituents, shaking hands, and kissing babies. As pleasantville is a big, important neighborhood, she's got a big red phone in her office, giving her a direct line to the president (or at least a top aide) 24 hours a day, 7 days a week. She's seen lonely_house up on a hill at the outskirts of town, but never really worried about who lives inside. The agoraphobic stays inside most of the time, playing solitaire and feeding a_cat. He's called the_mayor a few times to ask about local noise regulations, and even wrote ima_celebrity (Pleasantville's ima_celebrity, that is) some fan mail after seeing her on the local news. In addition to establishing a scope chain, each execution context offers a keyword named this. In its most common usage, this serves as an identity function, providing our neighborhoods a way of referring to themselves. We can't always rely on that behavior, however: Depending on how we get into a particular neighborhood, this might mean something else entirely. In fact, how we get into the neighborhood is itself exactly what this generally refers to. Four scenarios deserve special attention: * Calling an Object's Method In typical object-oriented programming, we need a way of identifying and referring to the object that we're currently working with. this serves the purpose admirably, providing our objects the ability to examine themselves, and point at their own properties. When new BigComputer() is executed, a completely new object is created transparently in the background. BigComputer is called, and its this keyword is set to reference that new object. The function can set properties and methods on this, which is transparently returned at the end of BigComputer's execution. Why does this mean something different inside the_question than it does inside BigComputer? Put simply, we entered BigComputer via new, so this meant "the new object." On the other hand, we entered the_question via deep_thought, so while we're executing that method, this means "whatever deep_thought refers to". this is not read from the scope chain as other variables are, but instead is reset on a context by context basis. Here, this defaults to reference the most global thing it can: for web pages, this is the window object. What does this mean when the event triggers our function's execution? Unfortunately, there's not a simple answer to this question. If we write the event handler inline, this refers to the global window object: <script type="text/javascript"> function click_handler() { alert(this);
What if instead of running click_handler, we wanted to ask deep_thought a question every time we clicked the button? The problem is simply this: We've passed off a reference to the ask_question method, which, when executed as an event handler, runs in a different context than when it's executed as an object method. In short, the this keyword in ask_question is pointing at the DOM element that generated the event, not at a BigComputer object. The DOM element doesn't have a the_answer property, so we're getting back undefined instead of "42." setTimeout exhibits similar behavior, delaying the execution of a function while at the same time moving it out into a global context. This issue crops up all over the place in our programs, and it's a terribly difficult problem to debug without keeping careful track of what's going on in all the corners of your program, especially if your object has properties that do exist on DOM elements or the window object. call() We really do want to be able to ask deep_thought a question when we click the button, and more generally, we do want to be able to call object methods in their native context when responding to things like events and setTimeout calls. Two little-known JavaScript methods, apply and call, indirectly enable this functionality by allowing us to manually override the default value of this when we execute a function call. Let's look at call first: <script type="text/javascript"> var first_object = { num: 42 }; Then we define a multiply function that accepts a single argument, and returns the product of that argument, and the num property of its this object. If we called that function by itself, the answer returned would almost certainly be undefined, since the global window object doesn't have a num property unless we explicitly set one. We need some way of telling multiply what its this keyword ought refer to; the call method of the multiply function is exactly what we're looking for. The first argument to call defines what this means inside the executed function. The remaining arguments to call are passed into the executed function, just as if you'd called it yourself. call(first_object, 5) is executed, the multiply function is called, 5 is passed in as the first argument, and the this keyword is set to refer to object first_object. call(second_object, 5) is executed, the multiply function is called, 5 is passed in as the first argument, and the this keyword is set to refer to object second_object. apply works in exactly the same way as call, but allows you to wrap up the arguments to the called function in an array, which can be quite useful when programatically generating function calls. Replicating the functionality we just talked about using apply is trivial: <script type="text/javascript"> ...
Instead of providing a function reference to the onclick handler, we're giving it the result of an executed function. We need to exploit another feature of JavaScript to really solve this problem.
Prototype JavaScript framework, but I am very much impressed with the quality of its code as a whole. In particular, one simple addition...
|