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
}
});
});
this
is the solution to the problem. However, I could not find a canonical answer which explained what was wrong with usingthis
in that fashion. Most answers were single line answers suggesting to storevar that = this
and move on. In order to try to reduce the amount of time people have to answer these types of questions I hoped to provide a canonical answer here. Please edit or request changes to it if anything is either incomplete or in need of correction.this
/ context inside a callback? was the (non-jQuery-specific) canonical question. Not sure whether it would help to merge them.this
is used with this type of structure. jQuery tends to obfuscate it a little and so I was attempting to explain the process involved. I think that the linked question from Felix addresses the non-jQuery aspect very well.this
in JavaScript changes in different locations. It can also be changed programmatically via.apply
or.call
.apply
on callback functions in order to bindthis
to the current element. I outline this in the answer below.