Save access to this scope
Explanation of the problem
this changed
The value of this
MDN has changed and is no longer referencing the expected element or value. Often this is because the scope has changed, and as a result so has the this
reference.
this is contained in an execution context
The scope refers to the current Execution ContextECMA. In order to understand why this
has changed, it is important to understand the way these execution contexts operate in JavaScript.
execution contexts bind this
When control enters an execution context (code is being executed in that scope) the environment for variables are setup (Lexical and Variable Environments - essentially this sets up an area for variables to enter which were already accessible, and an area for local variables to be stored), and the binding of this
occurs.
the this binding was changed for the execution context
These contexts form a logical stack. The result is that contexts deeper in the stack have access to previous variables, but their bindings may have been altered. Every time jQuery calls a callback function, it alters the this binding by using apply
MDN.
callback.apply( obj[ i ] )//where obj[i] is the current element
The result of calling apply
is that inside of jQuery callback functions, this
refers to the current element being used by the callback function.
For example, in .each
, the callback function commonly used allows for .each(function(index,element){/*scope*/})
. In that scope, this == element
is true. The consequence of that, is if you expected a previous this
to be available, it will be bound to a different element.
Brief explanation of this
in jQuery callbacks
As shown above, jQuery callbacks use the apply function to bind the function being called with the current element. This element comes from the jQuery object's element array. Each jQuery object constructed contains an array of elements which match the selectorjQuery API that was used to instantiate the jQuery object.
$(selector)
calls the jQuery function (remember that $
is a reference to jQuery
, code: window.jQuery = window.$ = jQuery;
). Internally, the jQuery function instantiates a function object. So while it may not be immediately obvious, using $()
internally uses new jQuery()
. Part of the construction of this jQuery object is to find all matches of the selector. The jQuery object then contains an array-like structure of the DOM elements matching the selector.
When a jQuery api function is called, it will internally iterate over this array-like structure. For each item in the array, it calls the callback function for the api, binding the callback's this
to the current element. This call can be seen in the code snippet above where obj
is the array-like structure, and i
is the iterator used for the position in the array of the current element.
Finding a solution
It can be hard to determine what happened since jQuery tends to fail silently. .data()
jQuery API is still a valid call here, it just returns undefined
. As a result, the above code produces a class name of "Activate"+undefined, "Activateundefined".
The important part to recognize here is that jQuery has changed the this
binding. In order to use a previous this
binding, the value must be stored in a variable. A common name for storing this is that
, self
, me
, or in best practice, an actual description of what this
represents.
The reason saving the binding works is that the callback's execution context will be deeper in the execution context stack relative to where the binding value was saved, thus having access to that stored value.
jsFiddle Demo
$('#toggleButton').click(function(){
var toggleBtn = this;
//^ store this into the toggleBtn variable
$("#toggleSet div").each(function(index,element){
//^ binds `this` to the current element
if( element.innerHTML != "Skip" ){
//^ we could have used `this.innerHTML` here
$(element).toggleClass("Activate"+$(toggleBtn).data("color"));
//^ re-use the stored `this` value
}
});
});