Forum Moderators: open

Message Too Old, No Replies

Can I use $(this) after an if (element) {.} ?

in jQuery

         

csdude55

9:43 pm on Feb 8, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



This is a jQuery question...

I have a series of these:

if ($('#foo').length) {
$('#foo').hide();
}


I know that if I use $(this) where applicable then it doesn't make a DOM query, so it's marginally faster. And since $(this) is shorter than most of my ID selectors, it would make for a faster download of the file, too.

So the question is, is there a way that I can reference the element from the condition (ie, foo) within the if() without rewriting it?

Like this, but of course it doesn't work:

if ($('#foo').length) {
$(this).hide();
}
]

robzilla

11:32 am on Feb 9, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



The obvious answer would be that you should store $('#foo') in a variable prior to the if statement.

If you insist on employing if(), a slightly messier option could be:
if ((foo = $('#foo')).length) {
foo.hide();
}

I hope it's also obvious that these are micro optimizations and that no-one's going to notice the difference. That is not to say they're not theoretically interesting questions, though, which, if you have the time, can still be worth your time :-) (And frankly I like asking these types of questions myself.)

csdude55

8:26 pm on Feb 9, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Does that still not make a second DOM query?

FWIW, I couldn't get the assignment within the condition to work, jsfiddle said Expected a conditional expression and instead saw an assignment. You can see it here:

[jsfiddle.net...]

This worked, though:

var a = $('#foo');
if (a.length)
a.hide();


I hope it's also obvious that these are micro optimizations and that no-one's going to notice the difference.

True, but here's what's interesting... as I've been doing my rebuild and I keep making these micro optimizations, I've cut the load time of my homepage from 3.779s to 2.488s! With a lot of my audience on dial-up, slow mobile connections, and older people with computer viruses, that's a significant improvement. And time saved often results in more pages per session, which translates to more ad impressions... more $$$ :-D

I probably wouldn't start anything over to do a small improvement, of course, but since I'm rebuilding anyway... why not?

robzilla

12:33 am on Feb 10, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



~1.5s is certainly not micro ;-)

There's an error in the fiddle, (a = $('#foo').length) should read ((a = $('#foo')).length). You don't need to include var a; either. [jsfiddle.net...]

Functionally it's the same as:
var a = $('#foo');
if (a.length)
a.hide();

csdude55

3:46 am on Feb 10, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



~1.5s is certainly not micro ;-)

For real! I'm tickled with that, honestly. And it's aesthetically the same, I've just made a thousand little tweaks here and there.

There's an error in the fiddle, (a = $('#foo').length) should read ((a = $('#foo')).length).

Haha, my mistake! Thanks for the catch :-)

You don't need to include var a; either.

I see that, but now I have to ask... why not? I thought I needed to declare any and all variables before using them?

robzilla

10:20 am on Feb 10, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Generally, yes. I'm not entirely sure why it's not required in this context. I believe it works in C++, too.

Initializing the variable first is considered good practice, and the above shorthand won't work in strict mode.

NickMNS

4:28 pm on Feb 10, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



You don't need to include var a; either.

Declaring the variable determines it's scope. You may choose to declare the variable outside of any functions thus making it global and then assign its value from within a specific function. Or you could declare the variable from within a function thus limiting it's scope to that function.

Regarding the fiddle demo, It makes no sense to declare the variable, but not assign a value to it, just to simply assign it's value on the next line, from within an if statement. Can you do it? Yes. Should you do it? No.

Does that still not make a second DOM query? (this refers to assigning a queried element to a variable)

No by assigning the "queried element" to a variable it assigns a reference to that element (DOM node). When you later access the variable the reference is there, so no further DOM queries are required. It is also worth pointing out, that since it only assign a reference to the element, if your element is changed by some other function then that change will be reflected when the variable is accessed again, whether or not this is desired is depends on the code.

As for the $(this) question, $(this) does not refer to the last element selected, it refers to the object of a method. Since an if statement is not a method, the code as posted in the OP, will not work.

$('#foo').someMethod(
$(this).doSomething()
)

The above pseudo-code "does something" with the #foo element because "someMethod" is called on the object '#foo'. Not because you queried the DOM to find "#foo".

robzilla

5:52 pm on Feb 10, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



If you're referring to my fiddle, the scope of the variable defined in the expression of if() is, in fact, global, so it's essentially the same as declaring var a; beforehand.

var a = $('#foo');
if(a.length) {
a.hide();
}

if((a = $('#foo')).length) {
a.hide();
}

The former is easier to read, obviously, and would have my preference, but the latter works, too.

Both are certainly better than:
if($('#foo').length) {
$('#foo').hide();
}

NickMNS

6:33 pm on Feb 10, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



If you're referring to my fiddle,

No I was referring to csdude55's fiddle:

$(function() {
var a;

// Expected a conditional expression and instead saw an assignment
if (a = $('#foo').length)
a.hide();

});


Here is a great "answer" from stack overflow that explains declaring vs not declaring a variable.
[stackoverflow.com...]

csdude55

9:35 pm on Feb 27, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Sorry for the bump, but using this gave me a logic error that took about days to figure out! So I'm hoping to help future readers...

Do NOT use the same variable on the page! It's obvious in hindsight, but if you reuse it then a failed test can give a false positive from the earlier usage. So THIS is a bad idea:

for (i=0; i < 10; i++) {
if ((a = $('#foo')).length)
a.hide();
}


I tried using if ((var a = $('#foo')).length), but this threw an error that "var" wasn't expected. So the only real solution was to use a unique variable for each test.

In my case, with the condition in a loop, the better solution ended up being to use an array. I DID have to declare the array in advance, though, so:

a = [];

for (i=0; i < 10; i++) {
if ((a[i] = $('#foo')).length)
a[i].hide();
}

I could have probably used a string variable and set var a; inside the loop to reset it each time, but this was a little easier for me to read.

Hope this saves some future reader the pain and suffering that I went through! LOL

NickMNS

11:51 pm on Feb 27, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



With all due respect I completely disagree with your approach, and you risk confusing yourself in the future. In fact I think you may already be confused, I am confused by your code.

First my confusion.
Why are you looping to hide a single element (an element defined with '#' means you are selecting it by id and an id must be unique). Each loop finds the same element so something doesn't jive. I assume this is only for the purpose of the example.

Note that to assign a value to an elements in an array, in this case, seems like a bad idea. Yes it solves your problem in the short term, but you risk consuming memory for no reason, if your counter is only until 10, then the risk is low, but if your counter can reach a high value this can cause you problems. Not to mention that if you are not limiting the scope of the variable, and using elsewhere for something else, you will completely loose control of what is being assigned and where.

The code below is using ES6 syntax, which you may not want use it due to compatibility issues.

for (let i=0; i < 10; i++) {
const a = $('#foo');
if (a.length) a.hide;
}

By using const instead of var, the variable "a" cannot be reassigned and has it's scope limited to the for loop block. Thus reusing variable "a" will not cause an issue elsewhere in the script. The same is true for "i".

In the case where you need to stick to ES5 syntax then you can limit the scope of the variable to a function.

function hideElement(elem) {
var a = $('#' + elem);
var i;
for (i=0; i < 10; i++) {
if (a.length) a.hide;
}
}
hideElement('foo');

With the above code the variable is limited to the scope of the function and thus using it elsewhere will not cause any issues.

I should also point out that assigning the '#foo' element to variable a, and then at some later point taking a.length is equivalent to a = $('#foo')).length. The difference is only in the syntax. IMO the latter is less readable.

csdude55

1:28 am on Feb 28, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I was trying to keep my sample code brief so that it made sense :-) That's twice now that I've done that, though, and just ended up making it more confusing! Sorry 'bout that.

In my live code, I'm not REALLY hiding anything, I was just using that in the example because it made sense. I'm actually showing a group of ads in an infinite scroll, so it looks like:

****
300x250 DFP
****
3 Local Banners
****
300x250 DFP
****
Random widget for something local
****

This group of 4 things repeats in the infinite scroll, so I don't know the ending number.

DFP uses an ID and I don't know a way around that, so the code looks like this:


$bCount = automagically figured out in advance;

echo <<<EOF
<div id="banner_1_$bCount"></div>

<div id="local_$bCount"></div>

<div id="banner_2_$bCount"></div>

<div id="widget_$bCount"></div>

<script>showBanner($bCount);</script>

EOF;


The script DOES get pretty complex and hard to read, but essentially I use the value sent to showBanner() to refresh banner_1_$bCount and banner_2_$bCount and then I use Ajax to populate local_$bCount and widget_$bCount. But the "a.length" variable here needs to be unique for each of them.

The loop actually only has 2 iterations, but the loop can be called an unlimited number of times as they scroll down. Do you think that would be a potential memory leak? It's not too late to just reset the variable within the loop before using it.

NickMNS

2:47 pm on Feb 28, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Let me suggest a completely different approach.
Instead of using id="banner|widget|local + counter", instead use class="banner|widget|local". Then select elements by class. I assume that $bCount is related to the page count of the infinite scroll, and thus there must be some div element that is the parent of the each page's elements. Eg:


<body>
<div id="page1">
<h1>page1</h1>
<div class="banner banner-top">banner-ad</div>
<p> page 1 content blah bla bla!</p>
<div class="banner banner-bottom">banner-ad</div>
</div>
<div id="page2">
<h1>page2</h1>
<div class="banner banner-top">banner-ad</div>
<p> page 2 content yadda yada yaddah!</p>
<div class="banner banner-bottom">banner-ad</div>
</div>
<etc...>
</body>


Then for the JS:

//to select all banner ads
var allBanners = document.querySelectorAll('.banner');
// this is a node list of all the .banner nodes, you can then loop through each one refreshing each one.

// to select banners on page 1
var pageOne = document.querySelector('#page1');
// selects the page1 node
var pageOneBanners = pageOne.querySelectorAll('.banner');
// as above this is a node list of all the .banner nodes but only those on page 1.


The advantage of this approach is that you no longer need to check the length to see if the element exists, the JS will only select items that exist in the DOM. Moreover the items are in a node list, so you can loop through it to do what you need to do. (note: a node-list is not an Array and needs to be handle differently but it is simple and well documented)

csdude55

4:49 pm on Feb 28, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Thanks, Nick, I love the idea! But I THINK that DFP requires an ID for the container, and I push the ad to it like so:

googletag.cmd.push(function() {
slot[x] = googletag.defineSlot('/12345/' + atf_rect,
[300, 250], 'banner_' + i + '_' + bCount) // 'banner_' + i + '_' + bCount = banner_1_$bCount
.addService(googletag.pubads());

googletag.display('banner_' + i + '_' + bCount);
googletag.pubads().refresh([slot[bCount]]);
}

The examples I've found all reference the banner ID without the '#', so I don't think that their system allows me to specify other selectors :-(

NickMNS

5:36 pm on Feb 28, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I THINK that DFP requires an ID for the container,

Yes this appears to be the case, but there is no reason that you can't keep an ID in place for this purpose, but for the JS purposes use the method above. When looping through the nodes you can easily get the nodes ID.

csdude55

5:53 pm on Feb 28, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Good idea... the script I have now is very complicated and hard to read, so I'll definitely use what you posted and see if I can make it a bit smoother. Thanks, Nick! :-D