16

Does anyone know how to tell if a cached jQuery object has gone stale, e.g. is no longer in the DOM? For example:

var $cached_elem = $('.the_button');

// .. and then later

$cached_elem.text('updating...');

I have recently encountered the situation where the $cached_elem is removed from the DOM due to some other event. So what I would like to do:

if ( $cache_elem.isStillInDOM() ){
  // now do time consuming stuff with $cached_elem in DOM 
}

Before anyone offers, I have already employed this, which is a fair analog for what I'm trying to do:

if ( $cached_elem.is(':visible') === true ){ ... }

However, this is not really the same thing and could fail in some cases.

So can anyone think of a simple way to check directly if a cached jQuery object is "stale"? I may be forced to write a plugin if not ...

Michael Mikowski
  • 1,269
  • 1
  • 10
  • 21

4 Answers4

26

if($elem.closest('body').length > 0) seems like it could do the trick.

$(function() {
    var $button = $(".the_button");
    alert (isStale($button));
    $button.remove();
    alert (isStale($button));
});
    
function isStale($elem)
{
    return $elem.closest("body").length > 0;
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
    <span class="the_button">Hello World</span>
</div>

Edit: Updated in response to Yi Jiang's comment so that it will return correctly if its parent element is removed

Edit 2: Updated in response to lonesomeday's comment - changed parents() to 'closest()` for performance improvement

Lionel Paulus
  • 378
  • 3
  • 6
Steve Greatrex
  • 15,789
  • 5
  • 59
  • 73
  • 1
    But what if I, say, remove the *parent* of the element? What you need to do I think is to detect if `body` is still one of the parent of the element in question, like this: http://jsfiddle.net/D9L8M/1/ – Yi Jiang Oct 28 '10 at 08:08
  • `$elem.closest('body').length` should be that little bit faster. You could also select on `'html`' if you were removing, say, a `link` element from the `head`. – lonesomeday Oct 28 '10 at 08:20
  • @lonesomeday - updated; @Yi Jiang - sorry about getting the name wrong! ;) – Steve Greatrex Oct 28 '10 at 08:25
  • @Michael Mikowski - if it solved your problem, can you accept that answer? – Steve Greatrex Oct 29 '10 at 07:22
  • @Steve Greatex answer accepted, although the other answer is also quite intriguing. However, this seems "cleaner". – Michael Mikowski Oct 30 '10 at 00:19
  • This example is incorrect. I'm not sure if it had to do with the last update or what but when the body `length > 0` it is NOT stale. The example has it backwards. I'm using [http://jsfiddle.net/raid5/dXnbm/2/](http://jsfiddle.net/raid5/dXnbm/2/) – raidfive May 04 '11 at 20:51
  • 2
    I find this kind of ironic. The point of caching an element is so that you can reference it later without traversing the DOM. In this answer, you start with the cached element, and then have to search through the DOM to find 'body' just to see if the element you have cached is still contained in 'body'. So now you have extra DOM traversing caused by your attempt to decrease DOM traversing (caching). The other answer below is even more ironic.... Doing the exact same DOM traversing that you have already cached, completely defeating the purpose of it being cached in the first place. – wired_in Dec 30 '13 at 17:59
  • using .closest is very slow http://jsperf.com/check-element-existence. jQuery.contains is faster `if (!jQuery.contains(document.documentElement, $elem[0]))` – dann Dec 01 '15 at 03:56
  • @wired_in You might access this DOM element variable a hundred times before you check if it's stale, in which case you've benefitted from caching. – Soulriser Dec 14 '16 at 23:43
  • @Soulriser The point is that this caching strategy makes no sense. If you have to traverse the DOM to check if it's stale, why are you even caching it beyond the scope in which it's guaranteed not to be stale? Let the variable that's storing the element go out of scope and create another jQuery element later if it needs to be used again, since the method for checking the cache is more expensive than actually requesting the selector again. – wired_in Jan 05 '17 at 22:29
  • FWIW I don't agree that this is a bad idea. Simply speaking it is useful in a test scenario where you want to check that your code is working correctly, a check that can be removed at production time. Meaning that you get a safety check in your testing/debugging code that is removed for performance reasons from production code (after your testing has verified that your code is good.) – Fraser Orr Aug 11 '18 at 15:58
9

The native document.contains() method should be faster than jQuery to determine if a cached jQuery element exists in the DOM.

if (document.contains($cached_elem[0])) {
    // Element is still in the DOM
}
Thomas Higginbotham
  • 1,662
  • 20
  • 25
  • 2
    I benchmarked and confirmed document.contains is much faster. $elem.closest( 'body' ).length > 0 takes about 1/81,000s, whereas document.contains() takes about 1/1,300,000s. So document.contains is around 16x faster. div_el = document.createElement('div'); document.contains( div_el ); // returns false. Good! document.body.appendChild( div_el ); document.contains( div_el ) // returns true. Remove the div and contains() again reports false. This also works when appended and deleted to the head and has good support in modern browsers. Excellent! – Michael Mikowski Jul 02 '15 at 21:10
  • 1
    `document.contains()` does not work in Internet Explorer. A workaround for IE is to use `document.body.contains()`. However, note that if the `$cached_elem` is the `document` itself, `document.body.contains()` will return false. I ended up using something like `if ($cached_elem[0] === document || document.body.contains($cached_elem[0])) { ... }`. – Robert Feb 13 '17 at 21:34
0

In a slightly variant case, one can directly compare the native DOM element wrapped by the jQuery object $cached_elem.

$cached_elem[0] === $($cached_elem.selector)[0]

Besides checking if the $cached_elem is still in DOM, this can tell you if it's still the original DOM element, rather than a re-created one with same attributes after certain/partial page refresh.

ryenus
  • 15,711
  • 5
  • 56
  • 63
0

If the selector hasn't changed, just reinitialize it:

var $cached_elem = $('.the_button');
// code that might remove some elements of $cached_elem
$cached_elem = $('.the_button');

If you want to avoid duplicating the selector string, use the selector property of the original jQuery object:

var $cached_elem = $('.the_button');
// code that might remove some elements of $cached_elem
$cached_elem = $($cached_elem.selector);
if ($cached_elem.length) {
  // element still exists
}
Jimmy
  • 35,686
  • 13
  • 80
  • 98
  • 1
    Yes, of course. But we're not using jQuery like that. We want to define the selector once, and then not worry about the original selector method for a number of good reasons. Examples include needing to refactor the caching mecha mechanism or the ability to introduce errors when the selector is duplicated in two or more places. Better if we could just say if $cached_elem.isStillInDOM() ) or if ( $cached_elem.isStale() ){... – Michael Mikowski Oct 28 '10 at 07:47
  • Why not just use a variable for the selector then? That way, if you need to change it in the future, there's no risk of forgetting to change one occurrence of it. You could even use the selector property of the original jQuery object: `$cached_elem = $($cached_elem.selector);` – Jimmy Oct 28 '10 at 07:58
  • Aha! That's the property I'm looking for, I think. So would this work: if ( $($cached_elem).selector).length < 1 ){ .... }? – Michael Mikowski Oct 28 '10 at 17:45
  • Your first closing paren is in the wrong place, but otherwise, yes. `if ($($cached_elem.selector).length) { // element still exists }` I have updated my answer to show this. – Jimmy Oct 28 '10 at 18:27
  • So let's just completely defeat the purpose of caching the element in the first place by traversing the DOM again for the same element. – wired_in Dec 30 '13 at 18:01