I was looking through Select2 (source code) and found each2 method prototype:
$.extend($.fn, {
each2 : function (c) {
var j = $([0]), i = -1, l = this.length;
while (
++i < l
&& (j.context = j[0] = this[i])
&& c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
);
return this;
}
});
My question is - how this method works? I mean - why there is while loop only with condition, without statement part? I'd really love to understand this method's flow.
When you put an expression inside a condition (for instance: if (i), if (i == null), if (++i), if (i < 2))) the expression get evaluated before its 'checked' is it true or false.
Live example:
we have var i = 0; now you call if (++i) console.log(i). The expression ++i return 1 (it is understood as truth in javascript [0 is not]) so console.log(i) logs 1.
Now let's say we have var i = 0 and if (++i && ++i) console.log(i). The first expressions returns 1 so the second is also called which returns 2. Both 1 and 2 are treated as truth so console.log(i) logs 2
Awesome. Let's have a look on the same example as above but before if we initialise var i = -1
. Now we call if (++i && ++i) console.log(i). The first ++i returns 0 which is falsity.
To continue you have to get how && works. Let me explain quickly: If you stack exp1 && exp2 && exp3 ... && expX then exp2 will be only executed(evaluated) when exp1 returns truth (for instance: true, 1, "some string"). exp3 will be executed only if exp2 is truthy, exp4 when exp3 is truth and so on...(until we hit expN)
Let's go back to f (++i && ++i) console.log(i) now. So the first ++i returns 0 which is falsity so second ++i isn't executed and whole condition is false so console.log(i) won't be executed (How ever incrementation was completed so i equals to 0 now).
Now when you get it I can tell you that while loop works the same in condition checking way. For instance var = -2 and while (++i) will be executed until ++i returns falsity (that is 0). while (++1) will be executed exactly 2 times.
TL;DR BELOW!!!
So how this works?
while (
++i < l
&& (j.context = j[0] = this[i])
&& c.call(j[0], i, j) !== false
);
I think the best way to explain is to rewrite it (:
while ( ++i < l ) {
if (!(j.context = j[0] = this[i])) break;
if (!(c.call(j[0], i, j) !== false)) break;
}
or ever less magical:
var i = 0;
while ( i < l ) {
if (!(j.context = j[0] = this[i])) break;
if (!(c.call(j[0], i, j) !== false)) break;
i++;
}
The purpose of the code is to add a new function each2 to the jQuery object that works similar to .each. The usage of this .each2 function is same as .each which executes over a jQuery object, takes a function to call back with 2 arg (index and value) and returns jQuery object.
The mysterious while loop,
while (
++i < l
&& (j.context = j[0] = this[i])
&& c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
);
return this;
}
Observations:
Continues "only if" all 3 conditions evaluates to true
First Condition: ++i < l, a simple iterate evaluates to true until the value of i is less than count of selected elements. [i is initialized to -1, because of the usage of ++i in next line and i is used as index in later references]
Second Condition: It is actually an initialization + null/undefined check. The condition is evaluated to true for all valid values and fails when there is an invalid context.
Third condition: The purpose of this condition allows you to break out of the loop if the call back function return false. Same as .each where return false inside the function will break the iteration.
As noted by OP, there is no statement after the while condition. Note the semicolon after the while loop, return this is only run after the while loop has been completed, and that is when any one of the three statements evaluate to false. If all you want to know is how the while loop is working, Filip Bartuzi's answer is probably the most complete. I will attempt to give a broader answer as to what the each2 method does.
As noted in the docs, it is just a more efficient version of jQuery's #each, or Array.prototype.forEach or Underscore's #each or lodash's #forEach that is designed specifically for select2's uses. It is used to iterate over any array or other iterable, wrapped in a jQuery object. So it is used for arrays of strings as well as jQuery collections.
The way it works is the scope (this) is the array that it was called on. It is given a function as an argument. This is exactly how the other each methods I previously mentioned are called. The function provided to each2 is called once for each item in the array. The while loop is sort of a hacky way to iterate over each item in the array, and call the function, setting the item as the context and passing the index in the array and the item as a jQuery object as the first and second arguments. Each statement in the while loop condition must be evaluated to determine if it is true, and that process is being used to actually assign values to variables, and increment i. The c.call(j[0], i, j) !== false part allows the function to terminate the loop early by returning false. If the function returns false, the while loop will stop, otherwise it will continue until i is greater than l, meaning that every item in the array has been used. Returning this afterwards just enables another method to be chained to the array after .each2.
Basically the while loop could be rewritten to:
var j = $([0]), i = 0, l = this.length, continue = true;
while (i < l) {
i++;
j.context = j[0] = this[i];
continue = c.call(j[0], i, j);
if (!continue) {
break;
}
}
but there are probably some performance reasons why that can't be optimized as well by the browser.
It is more fragile than the normal each methods. For example if an element in the array has a falsey value such as 0, (j.context = j[0] = this[i]) will be falsey, causing the loop to terminate. But it is only used in the specialized cases of the select2 code, where that shouldn't happen.
Let's walk through a couple of examples.
function syncCssClasses(dest, src, adapter) {
var classes, replacements = [], adapted;
classes = $.trim(dest.attr("class"));
if (classes) {
classes = '' + classes; // for IE which returns object
$(classes.split(/\s+/)).each2(function() {
if (this.indexOf("select2-") === 0) {
replacements.push(this);
}
});
}
...
^ This code is getting the classes from a dest DOM element. The classes are split into an array of strings (the string is being split on whitespace characters, that's the \s+ regular expression), each class name is an item in the array. No special jQuery work is needed here, so the 2 arguments that the function called by each2 is provided are not used. If the class starts with 'select2-' the class is added into a array called replacements. The 'select2-' classes are being copied, pretty simple.
group.children=[];
$(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); });
^ For brevity I have not included the rest of this method, but it is part of the process method. In this case, each2 is being used to recursively process a node and all it's children. $(datum.children) is a jQuery selection, each 'child' (named childDatum) will be processed in turn, and it's children will go through the same process. The group.children array will be used as a collection, whatever is added to that array for each childDatum will be available, so after the each2 is run it will hold everything that has been added during the processing of all the children, grandchildren, etc.
Related
Why does javascript's implementation of reduce skip execution for the first iteration?
[1,2,3].reduce((acc, val) => {
console.log('acc',acc);
console.log('val',val)
return acc + val;
});
// acc 1
// val 2
// acc 3
// val 3
// 6
I notice that the first statement execution never runs (in this case, I would have expected there to be 6 console logs, 2 for each element). This was very unexpected behavior when I was trying to execute a function with a side effect within each iteration with reduce.
In other languages that I've used, every iteration of the list passed executes. Are there examples otherwise?
Why does this happen and why is the implementation of javascript's native Array reduce like this?
========================= EDIT 1/Solution ========================
To make sure it goes through the first iteration, give it an initial value (the 2nd argument here/ 0 in this case)
[1,2,3].reduce((acc, val) => {
console.log('acc',acc);
console.log('val',val)
return acc + val;
}, 0);
That is because of the fact that on every iteration, the first value is treated as a return value (or the accumulator).
Straight from here, you can see
The accumulator accumulates the callback's return values; it is the
accumulated value previously returned in the last invocation of the
callback, or initialValue, if supplied (see below).
If we look at the source code here, we can see how it's implemented:
Array.prototype.myReduce = function(callback, initialVal) {
var accumulator = (initialVal === undefined) ? undefined : initialVal;
for (var i = 0; i < this.length; i++) {
if (accumulator !== undefined)
accumulator = callback.call(undefined, accumulator, this[i], i, this);
else
accumulator = this[i];
}
return accumulator;
};
In the else structure, we can see that if the value is undefined, we set it to the i-th subindex in the array; which, for the first iteration, is the first one. After that, it becomes the callback (return) value of the iterations which follow.
If you want, you can backtrack and check the output.
I am in the midst of completing some JavaScript algorithmic challenges and I had to factorialize a number as part of one of them. After searching through stack and other places I entered a correct code block:
function factorialize(num) {
if(num === 0) {
return 1;
}
if(num < 0 ) {
return undefined;
}
for(var i = num; --i; ) {
num *= i;
}
return num;
}
factorialize(5);
It it returns a correct result. What I am struggling to understand however is why the for loop doesn't have a second statement, and why it can run for ever? I have an inkling it's because as soon as i value is 0, any subsequent negative number that is generated will be multiplied by 0 and so only the integer numbers will form the result. But why does the function return a valid number, if the loop is still running to -infinity and hasn't been told to stop when reaching a certain value?
the second part of your for loop is the Condition:
An expression to be evaluated before each loop iteration. If this expression evaluates to true, statement is executed. This conditional test is optional. If omitted, the condition always evaluates to true. If the expression evaluates to false, execution skips to the first expression following the for construct.
Once --i reaches 0, it evaluates to false (falsey) and the for "exits"
adding a console.log(i) to your for loop will help demonstrate that
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for
All elements in a for-loop's expression are optional.
The second part of the for-loop is used to express the for-loop's condition. The condition is evaluated on every iteration, and when it evaluates to false, the loop is exited.
In this case, that second part that expresses the condition is --i. This means that on every iteration, i will be decremented by 1, until it finally reaches zero (0). Since 0 is considered to be a falsey value in Javascript, the loop exits.
for (a; b; c)
is a direct equivalent to
a;
while (b) {
c;
}
You're mistaken here because you seem to think that c is what determines the end of the loop, but b actually is. You can omit the third loop statement if you don't need it (as well as the first one).
Here, your loop is equivalent to:
var i = num;
while (--i) {
num *= i;
}
while (--i) does two things:
Decrement i (before anything else)
Check that i != 0 (this is the actual loop condition).
If it was i--, note that the operations would be done in the opposite order.
If you find the code hard to read, you can also write it this way for the same results:
for (var i = num - 1; i != 0; i--) {
num *= i;
}
Normally you would have :
for(var i = 0; i < num; i++)
the second statement is a boolean continuation expression.
So when you have
for(var i = num; i--;)
The second is still a boolean continuation expression and when i get to 0 it evaluated to false.
I'm following an online course about Javascript Functional Programming
at the Exercise 16 it show you how reduce is actually implemented, in order to help you understand how to use it, but into this implementation there is something i don't actually get, i'll show the code:
Array.prototype.reduce = function(combiner, initialValue) {
var counter, accumulatedValue;
// If the array is empty, do nothing
if (this.length === 0) {
return this;
}
else {
// If the user didn't pass an initial value, use the first item.
if (arguments.length === 1) {
counter = 1;
accumulatedValue = this[0];
}
else if (arguments.length >= 2) {
counter = 0;
accumulatedValue = initialValue;
}
else {
throw "Invalid arguments.";
}
// Loop through the array, feeding the current value and the result of
// the previous computation back into the combiner function until
// we've exhausted the entire array and are left with only one value.
while(counter < this.length) {
accumulatedValue = combiner(accumulatedValue, this[counter])
counter++;
}
return [accumulatedValue];
}
};
I don't understand the first if statement, when it check for this.length what this actually mean?
Take note this is different from the reduce in ES5, which returns an value instead of an Array, this is used just as a sample for the learning purpose.
Array.prototype.reduce = function(...
is saying, "create a function on the prototype of Array" - this means that the new reduce function will be callable on all arrays, eg:
[1, 2, 3].reduce(...
This means you can also call it on empty arrays, eg:
[].reduce(...
Building on the comment:
If the array is empty, do nothing
You're working on an array, and when the function is called, this is set to the array that reduce was called on. This implementation of reduce assumes that if that array is empty (ie this.length === 0), you can't logically reduce it any further - there's nothing to reduce, so you can return the same empty array.
As pointed out by #Alnitak in the comments, this implementation of reduce is flawed as compared to the specification. A different implementation is available on the MDN for polyfilling older browsers.
What is the best way to implement a siulation of 'break' feature of a for-loop when you are iterating through an user/engine-defined function?
foreach([0,1,2,3,4],function(n){
console.log(n);
if (n==2)
break;});
I've thought in implementing foreach in a way that would break when the function returned 'false' - but I would like to hear thoughts on how that is normally done.
returning false is the most common way to do it. That's what jQuery's iterator function .each() does:
We can break the $.each() loop at a particular iteration by making the
callback function return false. Returning non-false is the same as a
continue statement in a for loop; it will skip immediately to the next
iteration.
And its very simplified implementation:
each: function( object, callback ) {
var i = 0, length = object.length,
for ( var value = object[0];
i < length && callback.call( value, i, value ) !== false; // break if false is returned by the callback
value = object[++i] ) {}
return object;
}
This is so simple I am baffled. I have the following:
var x = 'shrimp';
var stypes = new Array('shrimp', 'crabs', 'oysters', 'fin_fish', 'crawfish', 'alligator');
for (t in stypes) {
if (stypes[t] != x) {
alert(stypes[t]);
}
}
Once the values have iterated it starts returning a dozen functions like
function (iterator, context) {
var index = 0;
iterator = iterator.bind(context);
try {
this._each(function (value) {iterator(value, index++);});
} catch (e) {
if (e != $break) {
throw e;
}
}
return this;
}
What the heck is going on?
Edit: In these scripts I am using http://script.aculo.us/prototype.js and http://script.aculo.us/scriptaculous.js I remember now reading about the way prototype extends arrays and I am betting this is part of it. How do I deal with it?
The for enumeration is going to go over every member of the object you passed it. In this case an array, which happens to have functions as members as well as the elements passed.
You could re-write your for loop to check if typeof stypes[t] == "function" or yada yada. But IMO you are better off just modifying your looping to only elements..
for(var i = 0, t; t = stypes[i]; ++i){
if (t != x) {
alert(t);
}
}
Or
for(var i = 0; i < stypes.length; ++i){
if (stypes[i] != x) {
alert(stypes[i]);
}
}
I wanted to migrate my last comment up to the answer to add the notice of the a caveat for the first type of loop.
from Simon Willison's "A re-introduction to JavaScript"..
for (var i = 0, item; item = a[i]; i++) {
// Do something with item
}
Here we are setting up two variables.
The assignment in the middle part of
the for loop is also tested for
truthfulness - if it succeeds, the
loop continues. Since i is incremented
each time, items from the array will
be assigned to item in sequential
order. The loop stops when a "falsy"
item is found (such as undefined).
Note that this trick should only be
used for arrays which you know do not
contain "falsy" values (arrays of
objects or DOM nodes for example). If
you are iterating over numeric data
that might include a 0 or string data
that might include the empty string
you should use the i, j idiom instead.
you want to do:
for (var i in object) {
if (!object.hasOwnProperty(i))
continue;
... do stuff ...
}
As for..in enumeration iterates over all properties (enumerable or otherwise) that exist on both the object and its prototype chain. The hasOwnProperty check restricts iteration to just those properties on the actual object you want to enumerate.
ES5 makes things a little better for library developers (and help avoid this stuff) but we won't see that ina shipping browser for quite a while :-(
[edit: replacing return with continue. lalalalala ;) ]
Since prototype has extended the array for your convenience you should take advantage of it. Your example could be rewritten as:
var x = 'shrimp';
var stypes = new Array('shrimp', 'crabs', 'oysters', 'fin_fish', 'crawfish', 'alligator');
stypes.without(x).each(alert);
It should be
for (t in stypes) {
if (t != x) {
alert(t);
}
}