When you are writing complex jQuery/javascript, how do you manage using this
without re-defining this
variables that were previously defined? Do you have a rule of thumb or a personal preference for naming your this
variables (as the nesting gets deeper)?
There are times where I want variables from a higher scope to be available to nested functions/callbacks, but then there are times when I'd like to have a clean slate/scope; is there a good way to call functions/callbacks without having to worry about variable collisions? If so, what technique(s) do you use?
Some super silly test code:
$(document).ready(function() {
console.warn('start');
var $this = $(this),
$dog = $('#dog'),
billy = function() {
console.log('BILLY!', 'THIS:', $this, ' | ', 'DOG:', $dog);
var $this = $(this);
console.log('BILLY!', 'THIS:', $this, ' | ', 'DOG:', $dog);
};
// (#1)
billy(); // BILLY! THIS: undefined | DOG: jQuery(p#dog)
// BILLY! THIS: jQuery(Window /demos/this/) | DOG: jQuery(p#dog)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // THIS: jQuery(Document /demos/this/) | DOG: jQuery(p#dog)
// (#2)
billy(); // BILLY! THIS: undefined | DOG: jQuery(p#dog)
// BILLY! THIS: jQuery(Window /demos/this/) | DOG: jQuery(p#dog)
$('#foo').slideUp(function() {
// (#3)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // BILLY! THIS: undefined | DOG: jQuery(p#dog)
var $this = $(this); // (#10)
// (#4)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // BILLY! THIS: jQuery(Window /demos/this/) | DOG: jQuery(p#dog)
});
$('#clickme').click(function() {
// (#5)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // THIS: undefined | DOG: jQuery(p#dog)
var $this = $(this);
// (#6)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // THIS: jQuery(button#clickme) | DOG: jQuery(p#dog)
$('#what').animate({
opacity : 0.25,
left : '+=50',
height : 'toggle'
}, 500, function() {
// (#7)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // THIS: undefined | DOG: jQuery(p#dog)
var $this = $(this);
// (#8)
console.log('THIS:', $this, ' | ', 'DOG:', $dog); // THIS: jQuery(div#what) | DOG: jQuery(p#dog)
});
});
// (#9)
billy(); // THIS: undefined | DOG: jQuery(p#dog)
// THIS: jQuery(div#foo) | DOG: jQuery(p#dog)
console.warn('finish');
});
A full demo page can be found here (jsbin.com).
Note: As you can see, I've "marked" the comments with numbers (#XX) for easy reference.
Marker (#1)
BILLY! THIS: undefined | DOG: jQuery(p#dog)
Rhetorical question: Why is $this
undefined, but $dog
is accessible?
Answer: Because the var
within that scope is re-defining $this
; it's just that I'm trying to log $this
before its been defined within that scope.
If I comment out var $this = $(this);
, then the marker (#1) returns:
BILLY! THIS: jQuery(Document index2.html) | DOG: jQuery(p#dog)
BILLY! THIS: jQuery(Document index2.html) | DOG: jQuery(p#dog)
The same logic applies to markers (#2), (#3), (#4), (#5), (#6), (#7) and (#8).
Based on this observation (and please correct me if I'm wrong here) I'm assuming that I could put var $this = $(this);
at the bottom of the function, and the current scope would know that I want to use the current scope's $this
(even though it's not defined yet), and not the parent's $this
(even though it IS defined).
$this
CONFLICTS:If one wants to cache $(this)
outside/within other closures/functions/callbacks and avoid collisions, then one should use different variable labels like these (for example):
var $$ = $(this);
var $this2 = $(this);
var $t = $(this);
var $that = $(this);
Is the solution above how you would avoid $this
collisions? If not, what's your prefered technique?
Marker (#9)
THIS: undefined | DOG: jQuery(p#dog)
... $this
is undefined for reasons mentioned above, but:
THIS: jQuery(div#foo) | DOG: jQuery(p#dog)
... $this
is now $('#foo')
!
Exactly why did this happen?
Is it because $this
was re-defined via marker (#10)?
(Hmmm, I feel like I need to Google "garbage collection in javascript".)
Again, when writing complex jquery/javascript, what's the best way to avoid this type of variable collision?
I hope these aren't horrible questions. Thanks in advance for taking the time to help me out. :)
You're actually running into variable hoisting issue:
billy = function() {
console.log('BILLY!', 'THIS:', $this, ' | ', 'DOG:', $dog);
var $this = $(this);
console.log('BILLY!', 'THIS:', $this, ' | ', 'DOG:', $dog);
};
is actually interpreted at runtime like this:
billy = function() {
var $this;
console.log('BILLY!', 'THIS:', $this, ' | ', 'DOG:', $dog); // $this hasn't been set to anything yet...
$this = $(this);
console.log('BILLY!', 'THIS:', $this, ' | ', 'DOG:', $dog);
};
JavaScript hoists variable and function declarations to the top of the scope block so you can reference them before manually declaring them. This gets people into trouble when they reference vars of the same name from outside of the scope block, as you can clearly see in your example.
You've just encountered the joys of scope and JavaScript. One thing a lot of people like to do is, if you need access to the current scope in a callback, define this as self, like so:
var $ = require('jquery')
, database = require('database')
$.on('click', function(action) {
var self = this;
database.connect(function() {
database.putSync(self.action.button);
});
});
Note that when 'connect' is called, the scope of "this" is reset to that of the callback and not the click-handler, which is why you've stored your scope in an accessible variable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With