Search code examples
javascriptjquerydomsetintervaldom-traversal

jQuery traversing the DOM using $(this) as a starting point inside setInterval/setTimeOut function?


I've been trying to understand how to (in jQuery) traverse the DOM using $(this) as a starting point inside a setInterval/setTimeOut function and have come across some behavior I have found baffling.

It doesn't seem to be possible to traverse the DOM using $(this) as a starting point inside a setInterval/setTimeOut function

Why is that?
jQuery/javaScript people - get on that.

Sample code to illustrate the behaviour:

jQuery:

// set this code anywhere (inside/outside - document.ready/named-function/...) and run it
var autoTimer = setInterval(function(){
    // INSERT EXAMPLE TEST-CODE FROM BELOW HERE
}, 10000)

Test code tried:

// from inside the setInterval/setTimeOut function above
// if $(this)
if($(this).length !== 0) {
    alert('hello');
} // alerts hello

// from inside the setInterval/setTimeOut function above
// if html-tag (body/head/html)
if($('body').length !== 0) {
    alert('hello');
} // alerts hello

// from inside the setInterval/setTimeOut function above
// if className
if($('.className').length !== 0) {
    alert('hello');
} // alerts hello

// from inside the setInterval/setTimeOut function above
// if id
if($('#id').length !== 0) {
    alert('hello');
} // alerts hello

So it finds specific elements inside the document just fine and apart from $(this) you can then traverse the DOM from there

ie.

// from inside the setInterval/setTimeOut function above
if ($('.className').find(something).length !== 0)... // works just fine

The problem comes when you want to use $(this) as a starting point for traversing

Traversing test code tried using $(this) as a starting point:

// from inside the setInterval/setTimeOut function above
// traversing up the DOM
if($(this).parents('body').length !== 0) {
    alert('hello');
} // no alert (same with closest/parentsUntil/etc up the DOM)

// from inside the setInterval/setTimeOut function above
// traversing sideways in the DOM
if($(this).siblings('body').length !== 0) {
    alert('hello');
} // no alert (same with next/prev/etc sideways in the DOM)

// from inside the setInterval/setTimeOut function above
// traversing down the DOM
if($(this).find('body').length !== 0) {
    alert('hello');
} // no alert (same with children down the DOM)

So starting from $(this) 'body' (or anything else for that matter) doesn't seem to exist anywhere (up/down/sideways) which is not true from any other starting point from above

I also tried using a named global function as in:

// set outside/inside document.ready
function testCode() {
    // INSERT EXAMPLE TRAVERSING TEST-CODE FROM ABOVE HERE
}

// set this code anywhere (inside/outside - document.ready/named-function/...) and run it
var autoTimer = setInterval(testCode, 10000) // no alert

// set this code anywhere (inside/outside - document.ready/named-function/...) and run it
var autoTimer = setInterval(function(){
    testCode();
}, 10000) // no alert

So to re-iterate my question:
Why does it seem impossible in jQuery to traverse the DOM (up/down/sideways) inside a setInterval/setTimeOut using $(this) as a starting point?

And what are possible hacks/work-arounds/etc. for this behavior?

Possible none-solutions for hacks/work-arounds/etc.

this answer using $.proxy only works if you use an older version of jQuery - but since as this document attests 'This API has been deprecated in jQuery 3.3'... at the moment this is not a solution.

And neither is the bind method since as the document attests 'As of jQuery 3.0, .bind() has been deprecated' and been superseded by the on method (but I can't see how I would use .on() here to any effect - but maybe that's just due to my lack of imagination).


Solution

  • This is a basic this scoping problem and has little to do with jQuery.

    The most straightforward approach is to assign this to another variable outside of the callback:

    var that = this;
    
    setInterval(function () {
        $(that).parents();
    }, 1000);
    

    If you don't care about IE, or if you have a build process, you can use an arrow function, which uses this from the scope outside it:

    setInterval(() => {
        $(this).parents();
    }, 1000);
    

    My personal preference is to avoid the use of this entirely because of all its cockamamie behavior that's giving you so much grief. It's not clear from your examples where your this value is coming from, but here are some ways you can avoid it:

    // inside .each()
    $('a').each(function (_, el) {
        $(el).parents();
    });
    
    // inside event handler
    $('a').on('click', function (event) {
        $(event.target).parents();
    });