JavaScript’s functional scoping makes the language incredibly fun and flexible. You can seamlessly adopt almost any paradigm with ease. It does come with one caveat: confusion over this
. Because only functions create scope, and because the context in which functions execute is mutable, what this
actually points to is confusing at first.
Let’s take a simple Java class.
Using this template we can create a new student and print out the relevant information.
Student me = new Student("Joshua", 26);
System.out.println(me);
In these methods, this
refers to the instance of Student
that we created. this.name
is "Joshua"
and this.age
is 26
. The function is always executed against the current object. this
is a predictable known quantity. Coming from this background JavaScript’s flexibility in regard to this
and function context comes as a shock.
Let’s take a quick example.
Looks standard enough, right? The prototype is extended to add a function that interacts with the base object, and existing variables are used to add functionality. Straight forward non-async JavaScript will rarely have an issue with scope. Asynchronous code will often have scope issues.
Extending this example to add an asynchronous call to a hypothetical remote service checking a minimum drinking age.
The potential issue is on fn(this.age
. Here this
will not be pointing to the Student
object like many expect. Instead it points to the global context. Here is a slightly different method that allows us to specify the context.
Here’s what’s happening. First the drinking_age
function has lost the age
parameter and been modified to look in the current context for it instead (this.age
). Then when invoking Laws.drinking_age
we are using .call
, one of the three scope-changing functions inherent in all Function objects.
.bind(this, [, arg1, arg2, ...argN])
.apply(this[, arguments])
.call(this[, arg1, arg2, ...argN])
Each of these has a distinct role in adjusting the function context. .bind
returns a new function with new context. .apply
will execute a function in a context with an optional array to expand into arguments. .call
executes a function in a context with individual arguments.
Using this structure, we can even manipulate the contexts of anonymous functions on the fly to persist current scope without messy var self = this;
statements.
jQuery('.messages').hide(250, messages.cleared.bind(this));
Understanding scope is fundamental to an enjoyable experience writing JavaScript.