Hi!
About Lodash's forEach
function, and Lodash in general…
I told you that it "abstracts away from you the chore (and complexity) of looping", so that you can focus on what really matters for your application: the collection you want to iterate through, and the piece of logic you wish to be applied for each item.
You use forEach
like this:
// You first define "the collection you want to iterate through":
var myArray = ["lazy cat", "wild dog", "hidden Lorbo", "Alf", "POTUS"];
// You then define "the piece of logic you wish to be applied for each item":
var myFunction = function(element) {
// do something with the element
};
// And finally, you leverage Lodash#forEach:
_(myArray).forEach(myFunction); // looping chore/complexity abstracted away!
myFunction
above is just a definition, you are never calling it. At least not explicitly: you merely pass its reference (its name) to the forEach
function which will take care, behind the scenes, of looping through the elements of myArray
and of calling myFunction
on each step of the looping, passing in the current item as element
.
Now, how does forEach
really work behind the scenes? If you go to https://lodash.com/docs#forEach you'll see three icons at the top: # S N. Clicking on S will show you the source code on GitHub (you could also find it in your local lodash.core.js file, but it's just easier clicking on a button ;)).
It looks like this:
function forEach(collection, iteratee) {
var func = isArray(collection) ? arrayEach : baseEach;
return func(collection, getIteratee(iteratee, 3));
}
A quick note:
_(myArray).forEach(myFunction)
, the way we used it, internally gets transformed into_.forEach(myArray, myFunction)
, the way it's being defined above with two arguments.myArray
is known as the "collection", andmyFunction
as the "iteratee", aka. "the piece of logic you wish to be applied for each item".
A little bit of context: forEach
may process either arrays or hashes (objects), and lodash leverages a different function in each case (for efficiency reasons); therefore it first checks what type of collection it has been provided with. myArray
is an array indeed, so isArray(myArray)
returns true and forEach
stores a reference to the convenient arrayEach
function as func
. At this point, the second line "looks" like this at runtime:
return arrayEach(myArray, getIteratee(myFunction, 3));
You would rather expect something simpler, like arrayEach(myArray, myFunction);
, so what's getIteratee
all about? Well, that one's a bit complex, so long story short: lodash wants people to have full control over that "iteratee", so it's somewhat customizable. getIteratee
will take care of checking whether any customization has been applied globally, and also is able to fallback on a default iteratee shall no valid iteratee be provided. In our specific case, we're not doing anything fancy, so it eventually does call:
arrayEach(myArray, myFunction); // which is pretty close to the initial code, forEach(myArray, myFunction)
What's left for us is unveiling what exactly arrayEach
does. It's defined at https://github.com/lodash/lodash/blob/4.12.0/lodash.js#L459-L478 (to quickly find it, I looked for "function arrayEach" in my browser, and not just "arrayEach"):
function arrayEach(array, iteratee) {
var index = -1,
length = array.length;
while (++index < length) {
if (iteratee(array[index], index, array) === false) {
break;
}
}
return array;
}
A quick glance at it, and you'll notice the while
loop. They're using some (nice) tricks (for instance, combining the incrementation with the exit condition for instance (++index < length
)), but that sets aside, it looks like a good old while
loop! arrayEach
takes care of calling myFunction
(remember, it's known as iteratee
at runtime).
Some may refer to
iteratee
/myFunction
as a "callback". As the developer, you simply providedforEach
with a reference to the function using_(myArray).forEach(myFunction)
, and internally,forEach
/arrayEach
is "callingmyFunction
back" for you when the time is right.
What's super interesting is the way they are calling the iteratee:
iteratee(array[index], index, array)
In our example, at runtime, that evaluates to myFunction(myArray[index], index, myArray)
. So myFunction
is called with three arguments, on each step of the looping:
- the first one is the item at position
index
withinmyArray
- the second one is the
index
position itself, in case you need it - the third and last one is the entire array itself, in case you need it
By "in case you need it", I meant that you may define myFunction
one of those three different ways:
var myFunction = function(item) { … } // #1
var myFunction = function(item, index) { … } // #2
var myFunction = function(item, index, collection) { … } // #3
We're using #1 right now, expecting a single argument that's the current item:
var myFunction = function(element) {
// do something with the element
};
But depending on what "do something with the element" means, we might need more information that just the current element, so Lodash is nice and pushes that through arguments as well in case we need the intel.
Another interesting aspect of arrayEach
is the fact that whatever myFunction
returns gets compared to false
:
if (iteratee(array[index], index, array) === false) {
break;
}
It means that, if you implement myFunction
in such a way that it returns false
at some point (for a specific item), it will have the side effect of stopping the looping entirely (break;
). It's handy when you are, say, looking for something in an array:
// The following function takes care of searching for a specific element
// within an array. It will return true as soon as it finds it, false otherwise.
function search(array, element) {
var found = false; // The element we've not found just yet.
_(array).forEach(function(item) {
if (item == element) {
// We found the element!
// Let's acknowledge that, then break off the looping.
found = true;
return false;
}
});
return found;
}
Using it is as simple as this:
var myArray = ["lazy cat", "wild dog", "hidden Lorbo", "Alf", "POTUS"];
search(myArray, "hidden Jeetbo"); // returns false
search(myArray, "hidden Lorbo"); // returns true
Excellent!
Lodash already has something like
search
: https://lodash.com/docs#includes
So you can see how you'll be able to leverage helpers such as forEach
or includes
: either directly, or within your own functions. My search
example happens to be already covered by Lodash's includes
, so you'd rather use that helper, but as a general-purpose library, Lodash can only do so much. It's then quite likely that at some point, you would either:
- use Lodash's chaining feature to compose Lodash helpers: https://lodash.com/docs#chain
- extend Lodash with your own little helpers: https://lodash.com/docs#mixin (it is a slightly more advanced use-case, so no need for you to spend time studying it, but just remember that Lodash provides you with a default set of helpers, that you may extend)
Let me know if some of that stuff troubles you :)