admin管理员组

文章数量:1388911

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.

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.

Share Improve this question edited Oct 22, 2014 at 17:54 tadman 212k23 gold badges236 silver badges265 bronze badges asked Oct 22, 2014 at 17:40 nd_maciasnd_macias 8121 gold badge6 silver badges13 bronze badges 1
  • Did you had a chance to review the answers? Drop a ment as this is not a one line solution and it requires clarification in case if it is not clear. – Selvakumar Arumugam Commented Oct 29, 2014 at 4:39
Add a ment  | 

3 Answers 3

Reset to default 5 +50

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 pleted 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:

  1. Continues "only if" all 3 conditions evaluates to true
  2. 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]
  3. 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.
  4. 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 pleted, 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 plete. 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.

本文标签: javascriptSelect2 each2 methodhow does it workStack Overflow