CUTCODEDOWN
Minimalist Semantic Markup

Welcome Guest
Please Login or Register

If you have registered but not recieved your activation e-mail in a reasonable amount of time, please use our Contact Form for assistance. Include both your username and the e-mail you tried to register with.

Author Topic: JavaScript: Fastest way to iterate array-likes with non "loose false" values  (Read 97 times)

Jason Knight

  • Administrator
  • Full Member
  • *****
  • Posts: 140
  • Karma: +2/-0
    • View Profile
    • CutCodeDown -- Minimalist Semantic Markup
This is a technique I've used for a long time that is really fast when working with array-likes such as nodeLists. In the majority of usage scenarios this is still the fastest across all browsers for such a task. Let's say for example you were working on every <a href="#" class="closeModal"> tag on a page.

Code: [Select]
var anchors = document.getElementsByClassName('closeModal');
for (var i = 0, a; a = anchors[i]; i++) {
a.addEventListener('click', function(e) {
e.preventDefault();
window.history.back();
}, false);
}

The magic being

Code: [Select]
for (var i = 0, a; a = anchors[i]; i++) {
it's easy to forget that assignment also returns the value in JavaScript. In this case we define the two inner variables, use the result of the assignment as our test, and then increment the counter as normal.

Because anchors[anchors.length] would be undefined -- a loose false, the for loop will end when it can't find that array index.

Copying the value -- typically an object -- into a variable that's local in scope also makes it even faster the more times you access the local assignment, as JavaScript looks for locals before it goes looking for globals. More recently the local was created, the faster the lookup in FF/Chrome/Edge. Just beware legacy IE the most recently created is the slowest... because Microsoft.

The only drawback is that if any value inside the loop is "loose false" -- aka undefined, false, 0, empty string -- it will stop looping prematurely.

But with things like nodeLists that's never a problem.

It's just a really handy technique I don't see used a whole lot.


"It is amazing what can be accomplished when nobody cares who gets the credit." -- Kelly Johnson

jmrker

  • Newbie
  • *
  • Posts: 16
  • Karma: +1/-0
    • View Profile
I have a question about your snippit of post #1.   ::)
If you could spare the time would you explain the difference
between the my working version and your version that gives me errors?

Code: [Select]
<!DOCTYPE html><html lang="en"><head><title> Test Page </title>
<meta charset="UTF-8">
<meta name="viewport" content="width-device-width,initial-scale=1.0, user-scalable=yes"/>
<!-- Idea from: https://forums.cutcodedown.com/index.php?topic=27.0 -->
<style>
 a { text-decoration: none; font-size: 2em; }
</style>
</head>
<body>

<ul id="menu">
 <li><a> Test 1 </a></li>
 <li><a> Test 2 </a></li>
 <li><a> Test 3 </a></li>
 <li><a> Test 4 </a></li>
 <li><a> Test 5 </a></li>
</ul>

<pre id='demo'></pre>

<script>
console.clear();

(function (d, info) {
  const doc = (IDS) => document.getElementById(IDS);

/* working version */
  var anchors = d.querySelectorAll(info);
  for (const [i, a] of anchors.entries()) {
    a.href = '#!';
    a.addEventListener('click', function() { doTask(a,i); }, false );
  }
/* */

  function doTask(elem, cnt) {
    doc('demo').innerHTML = `# ${cnt} : ${elem.textContent}`;
  }
})(document,'#menu a');
</script>

</body></html>
When I substitute your snippit, I get console.log error
Code: [Select]
/* problem version */
  var anchors = d.querySelectorAll(info);
  for (var i=0, a; a=anchors[i]; i++) {
    a.href = '#!';
    a.addEventListener('click', function() { doTask(a,i); }, false );
  }
I don't see any difference in the logic in getting the 'a' value
between the array[] access and the .entries() access version.

Is there a difference between the collection of getElementsByClassName()
and document.querySelectorAll() functions?  I thought both return a collection.

Your version makes sense to me,
but it doesn't work as I expected.

The remainder of the program is the same.
« Last Edit: 6 Nov 2019, 08:09:24 pm by jmrker »

Jason Knight

  • Administrator
  • Full Member
  • *****
  • Posts: 140
  • Karma: +2/-0
    • View Profile
    • CutCodeDown -- Minimalist Semantic Markup
The differences I see is the stupid new "const" for nothing. (Not that it seems to serve any purpose other than breaking client-side code), the new array assignment that's not real-world deployable client-side either, the arrow functions that will instantly by their mere presence cause scripting to stop dead in its tracks in IE whilst making the code painfully and aggravatingly cryptic...

NOT a fan of these new techniques that like many newer "features" reek of bad ideas from other languages being shoe-horned into JavaScript because people coming from Java or Ruby to node.js "miss" them.

Though in this case, it's your use of const that's the problem as the "a" variables are isolated in scope existing for each iteration as a unique value. That block level scope means that every loop in your original gets its own "a" variable whist using "var" on mine means they do not. The value of A stops being block scope or an immutable reference.

NOT that I would be trying to pass values in/to and event handler given how it then interferes with "Event" and forces a situation where your perfectly good callback ends up wrapped in an anonymous function in a situation where anons are just slowing down the code and increasing the memory overhead.

Of course since your anchors have no scripting off href for graceful degradation, and have scripting only functionality, one has to question why they are <a> in the markup in the first place. Even with the scripting addition of the href="#" they're not navigable as <a>.

Don't even get me STARTED about the use of innerHTML or the slow backtick string BS.

If we skip fixing the markup issues and just talk the functionality, I'd have written that same thing -- if one were to use the querySelectorAll method -- as:

Code: [Select]
(function (d, info) {

var
demo = d.getElementById('demo'),
anchors = d.querySelectorAll(info);

for (var i = 0, a; a = anchors[i]; i++) {
a.href = '#';
a.setAttribute('data-count', i);
a.addEventListener('click', doTask, false);
}

function doTask(e) {
demo.textContent =
'# ' + e.currentTarget.getAttribute('data-count') +
' : ' + e.currentTarget.textContent;
e.preventDefault();
}

})(document,'#menu a');

Storing the count on the anchor as data- where we can retrieve it in the event. This way it's a property of the element instead of turning garbage collection into a nightmare with the enfeebled (and misleading) "const" crap. I have a similarly low opinion of "let".

Though in practice I'd use neither technique, instead walking the DOM.

Code: [Select]
(function (d, target) {

var
demo = d.getElementById('demo'),
li = d.getElementById(target).firstElementChild,
count = 0;

if (li) do {
li.firstElementChild.href = '#';
li.firstElementChild.setAttribute('data-count', count++);
li.firstElementChild.addEventListener('click', doTask, false);
} while (li = li.nextElementSibling);

function doTask(e) {
demo.textContent =
'# ' + e.currentTarget.getAttribute('data-count') +
' : ' + e.currentTarget.textContent;
e.preventDefault();
}

})(document,'menu');

Which even though it's about 90 bytes more code, would execute many, MANY times faster.

One of the keys to either technique being the storage of #demo as a VAR scope so that we aren't wasting time trying to get hold of it every blasted time the event first. We get it ONCE since methods like getElementById are are one of the slower operations you can call. Same goes for avoiding the use of querySelectorAll in favor of the DOM walk.

Though the real laugh is after gzip compression, the DOM method is typically (though not always) smaller. Your original gzipping to 317 bytes, my rewrite with the for loop going to 287 bytes, and the final DOM walking version being 300 bytes even.

But yeah, that wasteful const/let garbage, arrow function garbage, const of nonsense, etc, etc? Complex, convoluted, inefficient, and introduces performance issues on the back end that make me wonder "Just who thought adding this to JavaScript was a good idea?!?"

Probably the same people who think the RUST language is a good thing, when it's akin to someone looking at C++ and going "this is way too clear and concise, what can we do to **** this up and make programming even MORE difficult?"

I don't get the appeal, they don't do anything that couldn't be done cleaner, simpler, and faster without them. Of the many good and useful things added in the latest flavors of ECMAScript, they are the worst. Horrifically bad. Another of the many things that seem to have been created just to make the language larger, more complicated, and harder to use.

But what do I know? As a Wirth language fan I've always found block-level scoping a bit derpy. If function level is insufficient, you're likely ridiculously and painfully overthinking how your code should work.
« Last Edit: 7 Nov 2019, 01:49:13 am by Jason Knight »
"It is amazing what can be accomplished when nobody cares who gets the credit." -- Kelly Johnson

jmrker

  • Newbie
  • *
  • Posts: 16
  • Karma: +1/-0
    • View Profile
Thank you for your extensive descriptions.
I had been concentrating on the querySelectorAll() vs. getElementsByClassName()
actions as being the problem. 
It had not occurred to me to look at the var, let or const assignments as being  a source.

I often look at your code to see:
1. How it works.
2. How it compares to other solutions.
3. If I can understand the differences in other scripts.

You often go beyond my initial query,
but that is good for me and allows for further analysis.
I also like the difference of opinions often expressed in the responses.

However, in my question of post #2, it is the version with the 'const' that works
while your original (post #1) version with the 'var' does not. 
Contradicts your explanation for me.

If the scope of the 'const' is supposed to be the problem,
why does it not effect the 'var' scope as well?
Both are doing things within the confines of their block as far as I see.
Am I continuing to be missing something here?

Note: Both of your solutions of post #3 work.
Indeed, the version with the for(var..loop works with the for( const [ ]..) as well
Since the questions are for my personal understanding (not production code)
I just wanted to know why the difference in the
scripts of post #2 using your code of post #1.
« Last Edit: 7 Nov 2019, 11:14:05 am by jmrker »

Jason Knight

  • Administrator
  • Full Member
  • *****
  • Posts: 140
  • Karma: +2/-0
    • View Profile
    • CutCodeDown -- Minimalist Semantic Markup
However, in my question of post #2, it is the version with the 'const' that works
while your original (post #1) version with the 'var' does not. 
Contradicts your explanation for me.

The original post doesn't try to access 'i', the index variable. In fact it works almost identical to my post #3 version apart from using an anonymous function.

Both hook the event to determine the target, and not the outer container. That function(e) part is the key, where "e" ends up the event. Event.currentTarget is always the element the event is attached to... VERY important attribute to know.

So if you can attach your data to the element that you also attached the event to, you will have access to it.

As to the scope, because my original post doesn't rely upon the value of "a" (the individual anchor) or "i" (the index) inside the event handler, the scope of them var vs. let vs. const just doesn't matter.

Your handler tried to rely upon the scope of the index, and/or the element being accessed. The const[] assignment creates them permanently, whilst even just changing mine to use let or const would still not work...

Which is just part of why I consider let/const to be a very BAD addition to JavaScript. They result in complex, convoluted, hard to follow code that's a pain in the ass to understand much less maintain, and don't work even close to how the language is even supposed to work. Again, it reeks of something Java or C++ programmers want to shoe-horn into the language whether it fits or not.
"It is amazing what can be accomplished when nobody cares who gets the credit." -- Kelly Johnson

jmrker

  • Newbie
  • *
  • Posts: 16
  • Karma: +1/-0
    • View Profile
Thank you.
Your comments always show me I have much more to learn (and/or remember :) )!

coothead

  • Newbie
  • *
  • Posts: 30
  • Karma: +2/-0
  • I smile benignly
    • View Profile
    • coothead's stuff ~ an eclectic collection
Hi there Jason,

I notice that you comprehend  const and arrow functions
but are enamoured with them.

As I do not understand them at all, I share your dislike.  :o

Is it possible that you could convert this..

Code: [Select]
const doc = (IDS) => document.getElementById(IDS);

... into something that I might easily recognise and like?  8)

coothead
~ the original bald headed old fart ~

jmrker

  • Newbie
  • *
  • Posts: 16
  • Karma: +1/-0
    • View Profile
@coothead
That was my code insertion, so don't blame Jason.

I use it the same as
Code: [Select]
function doc(IDS) { return document.getElementById(IDS); }
because I mis-type so often.

I used the 'const' as to avoid interference with any other code snippits definitions
in either the global or local scope.

coothead

  • Newbie
  • *
  • Posts: 30
  • Karma: +2/-0
  • I smile benignly
    • View Profile
    • coothead's stuff ~ an eclectic collection
That was my code insertion, so don't blame Jason.

I'm not blaming Jason or anyone else.  ;D


I would just like him, if possible,  to translate it to a pre "const and
arrow function
"  code that I would recognise and understand.  8)

coothead
~ the original bald headed old fart ~

Jason Knight

  • Administrator
  • Full Member
  • *****
  • Posts: 140
  • Karma: +2/-0
    • View Profile
    • CutCodeDown -- Minimalist Semantic Markup
I would just like him, if possible,  to translate it to a pre "const and
arrow function
"  code that I would recognise and understand.

Code: [Select]
function doc(id) { return document.getElementById(id); }
Whist there is a small difference in how const works (in that it's a constant reference, not a constant value) and how arrow functions treat "this", for the most part every time you see an arrow function it's usually because someone is too lazy to type the extra characters of "function" and "return" because they want the language to be as cryptic as possible.

I'm just wondering why he had the "IDS" variable in uppercase since it's not a define/ actual constant.

Which in JavaScript you pretty much have to use defineProperty to create.

In this case, he wrote out a function so he didn't have to type document.getElementById... which is... eh. I wouldn't bother in most cases when you can have a SIF or IIFE that you can pass 'document' to. Particularly when the extra function just adds call overhead, more memory use, and slower execution to the mix.

NOT that I can talk with how the upcoming elementals 4 "make" function has grown in size.

Of course if you use SIF /IIFE and can live with just function level namespace blocks, there's no need for what const provides.
"It is amazing what can be accomplished when nobody cares who gets the credit." -- Kelly Johnson

coothead

  • Newbie
  • *
  • Posts: 30
  • Karma: +2/-0
  • I smile benignly
    • View Profile
    • coothead's stuff ~ an eclectic collection
Code: [Select]
function doc(id) { return document.getElementById(id); }

Thank you very much, your simplified code is now
totally understandable and immensely likeable  :)

I will sleep easily in my bed tonight.  8)

coothead
~ the original bald headed old fart ~

 

Advertisement