Jump to content
Larry Ullman's Book Forums

Advanced Question - What Is Happening In This For In Loop?


Recommended Posts

I'm learning from various sources and can usually puzzle out what's going on, but this has me a bit stumped. I came across this code at http://www.webdeveloper.com/forum/showthread.php?224180-JQuery-how-does-it-s-insides-work:

function X(css){ //dom utility

 

//a couple of node harvesters, using id and tag name...

function el(id){ return document.getElementById(id);}

function tags(elm){return document.getElementsByTagName(elm);}

 

//collect output:

var out=[];

if(css[0]=="#"){//id

out.push(el(css.slice(1)));

}else{//tags

out=out.concat([].slice.call(tags(css)));

};//end if id or tagName

 

//define some methods for the utility:

var meths={

hide:function(a){a.style.display="none";},

show:function(a){a.style.display="";},

remove:function(a){a.parentNode.removeChild(a);},

color:function(a){a.style.color=this||a.style.color;},

size:function(a){a.style.fontSize=this||a.style.fontSize;}

};//end meths

 

//bind the methods to the collection array:

for(var meth in meths)(function(n,m){

out[n]=function(x){out.map(m,x); return out;}

}(meth, meths[meth]));//next method

 

return out;

}//end X dom utility

 

 

 

 

For the life of me, I am having a hard time figuring out how the Javascript is running in the for loop binding the methods to the collection array. I realize this is advanced, but if someone could walk through what is happening, and how the methods are being called with variables correctly linked to their respective methods, it would be appreciated. This is cool, if only I could understand how it is occurring.

Link to comment
Share on other sites

I've read the entire thread many times, and I'm still working on understanding this. Changing the immediately invoked function expression to the wrapper function makes it clearer, but I'm still trying to understand the inner workings of what is going on.

 

My primary difficulty is understanding what is happening with the map method, what exactly is being assigned to what, and when and where the methods are being invoked when the X object is used. I understand the map method's definition, but this is not a simple example of its use. I understand this has something to do with functional programming?

 

It would be more helpful for me if things were made more explicit. I also don't understand how method chaining is being implemented here, although I know you can return "this" in designing functions to enable such a pattern - and indeed, if I change "return out" to "return this" inside the for/in loop, everything still works fine.

 

I'm getting there, but this is very tricky! It's very cool, though - this way lies making one's own framework, yes?

Link to comment
Share on other sites

All right, after tearing this function apart, reporting variable values at every step to both the console using console.log and also to the page using document.write, I think I understand what's going on, at least a bit more. The for/in loop seems to be creating methods and assigning JUST the function STATEMENT to the new method name, not invoking it; e.g., "out[hide]" = "function (x) {out.map(m, x);return out;}". The only time I can get "m" to return a value is WITHIN the for/in loop, otherwise I get the error "ReferenceError: m is not defined". I don't understand why "m" is not being replaced with the meths[meth] definition, but the linkage to it does appear to stick, I assume through closure? This is tricky. It doesn't help that Firefox's console.log does NOT always show the same info as document.write, so sometimes I get different messages back (document.write provided more info, sometimes console.log just showed "function" - not as useful).

Link to comment
Share on other sites

JasonC, I looked at this post a few days ago, and I apologize for not answering sooner, but your question is a beast and there's a lot to answering it.

Anyway, when I get some time in the next day or two, I'll do my best to explain everything that's going on.

Thanks.

Link to comment
Share on other sites

Okay, sorry for the delay in answering your question.

I will admit, this was a hard question for me too.

I had never used the Array map method before, and I realized after playing with it a bit that the implementation is pretty tricky, which is likely what is causing most of the confusion with function X.

 

Anyway, I'll do my best to answer your question, but I may end up telling you a bunch of things you already know and not really answer the question, in which case, please tell me.

 

For starters, I think the dude's code is a bit too abbreviated and hard to read, so let's start by cleaning it up a bit. The following is how I'd write the same code:

 

function X(css) {

 // Part 1

 function el(id) {

   return document.getElementById(id);

 }

 function tags(elm) {

   return document.getElementsByTagName(elm);

 }

 // Part 2

 var out = [];

 if (css[0] === '#') {

   out.push(el(css.slice(1)));

 } else {

   out = out.concat([].slice.call(tags(css)));

 }

 // Part 3

 var meths = {

   hide: function (a) {

     a.style.display = 'none';

   },

   show: function (a) {

     a.style.display = '';

   },

   remove: function (a) {

     a.parentNode.removeChild(a);

   },

   color: function (a) {

     a.style.color = this || a.style.color;

   },

   size: function (a) {

     a.style.fontSize = this || a.style.fontSize;

   }

 };

 // Part 4

 for (var meth in meths) {

   (function (n, m) {

     out[n] = function (x) {

       out.map(m, x);

       return out;

     }; // The original coder forgot this semicolon.

   }(meth, meths[meth]));

 }

}

 

I basically just spaced things out a bit more, adding an extra set of brackets to the for-in loop, etc. Also, I divided the code up into four parts, as marked by my comments. Lastly, the original coder forgot a semicolon at the end of the anonymous function assigned to out[n] in the for-in loop, which I added.

 

I'm going to assume that part 1 is not the issue, so we'll skip over talking about that. Also, I'm going to assume that part 2 is not a problem either. The concat-slice syntax is a bit tricky, but more than anything, it's just a simple way to turn the NodeList of DOM objects into an actual array of DOM objects. And as the original coder admitted, you don't actually need the concat method call here (at least, not for the basic getElementsByTagName method call).

 

So that leaves us with parts 3 and 4. I think the tricky aspects of part 3 are best explained after we discuss part 4, so let's talk about part 4 now.

 

For part 4, I'm going to assume that you know how a for-in loop works, so let's discuss the wacky function madness within the for-in loop.

 

First and foremost is the self-invoking function in the for-in loop. The self invoking function is the following part:

 

(function (n, m) {

 // Code here

}(meth, meths[meth]));

 

First off, a self-invoking function is a function that is executed immediately after it's defined. This may seem pointless, but it's great for creating a new scope in JS, which is the whole point here too. To break up the syntax, the first part is a regular function definition, which is the following:

 

function (n, m) {

 // Code here

}

 

That should look very familiar. Then, to immediately execute that defined function, we simply place a set of parentheses after the ending bracket. Also, a semicolon is required because the whole thing is now essentially a function call. We end up with the following:

 

function (n, m) {

 // Code here

}(meth, meths[meth]);

 

For this, the string stored in meth is passed to the n parameter, and the function reference stored in meths[meth] is passed to the m parameter.

 

And to finish off the syntax, an extra set of parentheses is placed around the whole thing. This extra set of parentheses is not necessary, but it has become a convention for self-invoking functions in JS, as it makes it easier to immediately spot a self-invoking function when you see the left parenthesis before the function keyword.

 

As for why we even want to use a self-invoking function in the first place comes down to the issue of closure. You probably already know what closure is, but just to sum it up, if we don't use a self-invoking function to create a closure within the for-in loop, then we'll end up getting only the final method in the meths object (i.e., size) attached to our DOM elements. To better illustrate this, here's what the for-in loop would look like without a self-invoking function:

 

for (var meth in meths) {

 out[meth] = function (x) {

   out.map(meths[meth], x);

   return out;

 };

}

 

The problem with this is that after the for-in loop ends (which is when the methods within the meths object would actually be called), the meth variable is always and only equal to "size", the last value meth has in the for-in loop. As such, the above essentially turns into the following:

 

for (var meth in meths) {

 out[meth] = function (x) {

   out.map(meths['size'], x); // Note the 'size' literal here.

   return out;

 };

}

 

So basically, whether we call the hide, show, remove, color or size method, only the size method will ever be executed, because that's what's bound to all of the method names. However, by using the self-invoking function, we can retain the original values of meths[meth] each time through the loop. That's why a self-invoking function is required. If this is still confusing, just ask. You might be better reading about JS closures on the Internet though.

 

Now, within the self-invoking function, we have an anonymous function being assigned to the out array with various string indexes. Something important to understand is the structure of the out array after this for loop. For example, let's say that that we have three a elements in the document and we execute X('a'). Right before the for-in loop in the X function, the out array has the following structure:

 

out[0] = DOM reference to 1st link
out[1] = DOM reference to 2nd link
out[2] = DOM reference to 3rd link

 

And then after the for-in loop, the out array has the following structure:

 

out[0] = DOM reference to 1st link
out[1] = DOM reference to 2nd link
out[2] = DOM reference to 3rd link
out['hide'] = Function reference
out['show'] = Function reference
out['remove'] = Function reference
out['color'] = Function reference
out['size'] = Function reference

 

And that above structure is what is returned at the end of the X function. For each of the five methods in the meths object assigned to the out array, the function references assigned to the out array elements are a bit tricky. To start with, the anonymous function is defined as being able to accept an argument x. This x can be anything. As it turns out, this x argument is used for passing arguments such as 'red' in color('red').

 

The first line in the anonymous function is the map method call. I imagine this is where the greatest confusion is. The thing that was so mysterious to me at first too is how the map method functions. It's very tricky and non-intuitive.

 

I found the following link to be the best explanation of the map method I came across:

https://developer.mozilla.org/en-US/docs/Javascript/Reference/Global_Objects/Array/map

 

The first thing to understand about the map method is the arguments it can take. The first is the function/method to call for all the elements in the array the map method is called for. In this case, because meths[meth] is mapped to the m parameter in the self-invoking function, the m that is the first argument for the map method is essentially meths.hide, meths.show, meths.remove, meths.color and meths.size as we progress through each iteration of the for-in loop.

 

The second argument for the map method is the value we want to have assigned to the this keyword within the method called for the first argument. In our code, because x is passed as the second argument, whatever value is passed to the original method call becomes the this keyword in the methods defined in the meths object. For example, if we call the color('red') method, then x becomes 'red', which then becomes "this" in the color method in the meths object.

 

The other important thing to understand is how the method called by the map method receives arguments. This part confused me a bit, but the article I linked to above cleared this up well. It says:

 

 

callback is invoked with three arguments: the value of the element, the index of the element, and the Array object being traversed.

 

As such, for each of the methods in the meths object, when they are called by the map method, the a argument defined for all the functions becomes "the value of the element". In other words, the a argument becomes the value of the individual elements within the out array.

 

Now, if you'll recall, our out array at the end of function X looks like the following:

 

out[0] = DOM object of a element
out[1] = DOM object of a element
out[2] = DOM object of a element
out['hide'] = Function reference
out['show'] = Function reference
out['remove'] = Function reference
out['color'] = Function reference
out['size'] = Function reference

 

So you're probably thinking, "Well, for the 0th, 1st and 2nd elements in the out array, the DOM references being passed to the a parameter makes sense, but it doesn't make any sense for the function references for the other five elements in the out array to be passed to the a parameter." Well, we're in luck here because of how the map method works.

 

As it turns out, the map method does NOT execute for the 'hide', 'show', 'remove', 'color' and 'size' methods. Why? Because the map method works by getting the length of the out array and then running a for loop based on that length value. And here's the tricky thing about the length property for arrays in JS:

 

The length property in JS calculates the length of an array by getting the highest number index and adding 1 to it. In other words, it does NOT count any associative (i.e., string) indexes. And since the highest number index is 2, out.length is always calculated as 3. If you don't believe me, output the length of the out array in function X right before the out array is returned and you'll see what I mean.

 

So, in the end, the map method is only executed for the first three elements in the out array, which are all DOM object references, which all become the a parameter in the functions assigned to the methods in the meths object.

 

Finally, the last line of code is the line after the out.map method call. To finish things off, the out array is returned. This is essential for chaining. Without this, you can't chain methods together. In other words, you could only execute one method at a time.

 

So, to tie all this together, let's walk through an example. For this example, let's imagine that we have three a links in our document and we execute the following:

 

X('a').color('red');

 

The first thing that happens is that the DOM references to the three a links in the document are stored in the out array. Our out array now looks like the following:

 

out = [DOM reference to the 1st link, DOM reference to the 2nd link, DOM reference to the 3rd link]

 

Next, the meths object is defined. After that, the for-n loop is run. At the end of the for-in loop, our out array looks like the following:

 

out = [0 => DOM reference to 1st link, 1 => DOM reference to 2nd link, 2 => DOM reference to 3rd link, 'hide' => function (a) { a.style.display = 'none'; }, 'show' => function (a) { a.style.display = ''; }, 'remove' => function (a) { a.parentNode.removeChild(a); }, 'color' => function (a) { a.style.color = this || a.style.color; }, 'size' => function (a) { a.style.fontSize = this || a.style.fontSize; }]

 

The above array is then returned by function X. So now, we have to imagine that whole array existing, and the color('red') method being called on that. In other words:

 

[0 => DOM reference to 1st link, 1 => DOM reference to 2nd link, 2 => DOM reference to 3rd link, 'hide' => function (a) { a.style.display = 'none'; }, 'show' => function (a) { a.style.display = ''; }, 'remove' => function (a) { a.parentNode.removeChild(a); }, 'color' => function (a) { a.style.color = this || a.style.color; }, 'size' => function (a) { a.style.fontSize = this || a.style.fontSize; }].color('red');

 

Just like any other method call, assuming the method actually exists in the object (which it does!), the method is called and executed. In our example, out['color'] is called.

 

When out['color'] is called with the 'red' argument, we essentially get the following function call:

 

out['color'] = function ('red') {

 out.map('color', 'red');

 return out;

}('red');

 

And remember, with the map method, the 'color' method in the meths object (which is now a part of the out array as well) is only called for the elements in the out array that have number indexes. So basically, the color method is called for the DOM references to the three links in our document and not the five function references stored in the out array. The call to the color method then essentially becomes the following:

 

function (DOM reference to 1st link) {

 DOM reference to 1st link.style.color = 'red' || DOM reference to 1st link.style.color;

};

function (DOM reference to 2nd link) {

 DOM reference to 2nd link.style.color = 'red' || DOM reference to 2nd link.style.color;

};

function (DOM reference to 3rd link) {

 DOM reference to 3rd link.style.color = 'red' || DOM reference to 3rd link.style.color;

};

 

This is where the links are actually changed to red. The important thing to remember is that these changes to the DOM objects are permanent and persist until the web page is closed. As such, when we return the out array at the end of the out['color'] method, the links stored in the 0th, 1st and 2nd array indexes are the links in a state in which the text color is red. As such, if we were to chain another method call onto the color('red') method call, then that method call would act on the out array returned (for which the links at 0, 1 and 2 are red).

 

Holy cow! That turned into an epically long answer. Unfortunately, I don't know if it answered any questions you have. Well, at this point, I'm not going to edit the whole post. I'll post it, and if there's anything you still don't understand, please ask.

 

Thanks.

  • Upvote 2
Link to comment
Share on other sites

Okay. This is probably the most challenging Javascript code I've tried to understand. I think I now understand theoretically what's going on. I'm wondering how one would change the code to make it more explicit what is happening while still keeping the X function syntax the same? Maybe using the call, apply, or bind methods?

 

I'm not a programmer by training, just someone who uses Javascript and finds it interesting. I know some BASIC and some C, so I'm more familiar with the imperative paradigm than OOP. So I'm learning, but this pattern seems to be unusual.

 

I find three things in this example especially interesting:

1) scope

2) use of out as a container like an array using BOTH associatively and numerically indexed elements, and the length of out does not include counting the associative elements

3) use of the closure (simple closures I can follow, but this one not easy to follow, at least for a beginner in closures) - it's hard to find m, for instance, in a defined state

 

Larry, an advanced Javascript book by you would be awesome.

Link to comment
Share on other sites

When you ask for more "explicit" code to accomplish the same thing, I assume that you're really asking, "What's a simple way to accomplish the same thing?"

 

I'm not sure if there's a simpler way, as "simpler" code might actually be more confusing for a JS guru, but for demonstration purposes, I have put together some sample code that achieves the same thing without the use of closures (at least, not in the sense that you're thinking about) and without the use of the map method.

 

Keep in mind that my code is super simplistic, and I wouldn't actually recommend ever doing things this way, but for demonstration purposes, I think it serves its purpose well.

 

<!DOCTYPE html>

<html lang="en">

 <head>

   <meta charset="UTF-8">

   <title>Chaining alternative</title>

 </head>

 <body>

   <a href="#" id="link1">Link 1</a>

   <a href="#" id="link2">Link 2</a>

   <a href="#" id="link3">Link 3</a>

   <script>

     // this in these two functions is equal to the out array in function X.

     function color(x) {

       for (var i = 0; i < this.length; i++) {

         this[i].style.color = x;

       }

       return this;

     }

     function size(x) {

       for (var i = 0; i < this.length; i++) {

         this[i].style.fontSize = x;

       }

       return this;

     }

     function X(css) {

       var out = [];

       // For this simplified example, I'm assuming that css is always a tag name, not an ID.
       var tags = document.getElementsByTagName(css);

       for (var i = 0; i < tags.length; i++) {

         out.push(tags[i]);

       }

       out['color'] = color;

       out['size'] = size;

       return out;

     }

     X('a').color('red').size('24px');

   </script>

 </body>

</html>

 

Does that help clarify things at all?

 

I know that that one guy's code on the other site is very difficult, so don't feel bad if it doesn't all click right away. He has a ton of complex code (and sometimes JS-specific idiosyncrasies) all stuck together.

 

To reply to your three points of interest:

 

1) Yes, scope in JS is very interesting. The thing about JS is that functions are lexically scoped, meaning that they contain the scope in which they're defined, not in which they are called. This is different from C, for example.

 

2) Yes, I agree that the way the length property is calculated in JS is interesting, but that's just the way it's defined in the ECMA specs (for better or worse). To give some more explanation, if an array contains only associative indexes, since that's akin to an object (not an array) in JS, the length will be 0. Another interesting caveat is that if, for example, you have an array with only 1 element, but the index of that element is 5, then the length will be reported as 6 (as per what I said in my previous post (i.e., 5 + 1)).

 

3) Closures are basically used as a tricky way to freeze a variable's state in time. For example, you may have a constantly changing global variable, but if you want to retain the value of that variable at one specific time, then you need to use a closure. Remember that every time a function is called, a closure is created (just usually not a closure in the sense that's commonly talked about). This happens because whenever a function is called, all the variables passed to that function are frozen in the state that they are in when the function was called. It's prudent to remember that this is NOT the case with function references/function definitions; only function calls. Anyway, for the m example, if you just remember that m is equal a specific instance of meths[meth] each time through the for-in loop, then you should be fine.

 

Hope that helps (and by all means, fire back another comment if it doesn't).

  • Upvote 3
Link to comment
Share on other sites

Well, that is about as advanced as JS gets, so if you get it, then you can get anything, I think.

Besides, the need to write code like that is super rare.

 

And for the record, the code was confusing for me at first too.

I had never used the map method before, and that threw me for a loop for a bit.

Link to comment
Share on other sites

I didn't before you simplified it, but your last version was easy to understand. Eistein's quote about explanations is so true. I'm really amazed by your Javascript knowledge.

 

You actually use map every time you use an associative array in PHP. I'm guessing you know this, but if you find data structures interesting, you should watch this Youtube video from PHP UK Conference. I've studied data structures and find them very interesting, but I generally think PHP developers would gain a lot by learning more about them. PHP developers take them for granted as arrays can do so much.

 

Not really a "must see" by any stretch of the imagination, but I would recommend it for anyone interesting. :)

 

Sorry about the off topic, btw. Guess this thread has more theoretical value than anything else, so maybe it's not that bad.

Link to comment
Share on other sites

I started watching that video, and it is very good, but I'll need some more time to finish later. I have bookmarked it though, Antonio. Thanks.

 

And yes, I agree with you, structuring data is something that a lot of people (including myself) take for granted, but it's very important, especially with larger applications.

Link to comment
Share on other sites

  • 6 months later...
  • 3 weeks later...
 Share

×
×
  • Create New...