0

I have a forEach function defined to do "something" with all the items in an array:

function forEach(array, action) {
  for (var i = 0; i < array.length; i++)
  action(array[i]);
} 

var numbers = [1, 2, 3, 4, 5], sum = 0;

So I could do:

forEach(numbers, console.log);

And it would print out all the numbers because 'console.log(array[i])' prints the number to the console. I get that part.

Here's where I'm stuck: If I pass the function below into the place of the action parameter, instead of 'console.log' then at what point does the function know about every element?

forEach(numbers, function(number) {
  sum += number;
});

console.log(sum);
// 15

How does it get evaluated? If I pass console.log into the last problem and it still has to be evaluated as 'console.log(array[i])' with the '(array[i])' code next to whatever parameter is being passed in, then doesn't that get applied to the entire function too since that function is the parameter? Such as below:

function(number) { sum += number; }(array[i])
Huangism
  • 16,278
  • 7
  • 48
  • 74
  • 1
    P.S. You can't *actually* pass `console.log` as `action`. It won't work. Other functions will, but `console.log` needs to be called in the correct context (the `this` value needs to be set correctly). You'd need to do `action.call(console, array[i])`. Or pass `console.log.bind(console)` instead. – gen_Eric Oct 06 '14 at 19:44
  • 2
    Are you saying you've defined your own forEach function? You know that already exists, right? http://dochub.io/#javascript/array.foreach – idrumgood Oct 06 '14 at 19:45
  • @RocketHazmat: It does work in some browsers where `console.log` is bound by default (which seems only reasonable) – Bergi Oct 06 '14 at 19:45
  • @Bergi: Which browsers do that? Chrome sure doesn't. – gen_Eric Oct 06 '14 at 19:45
  • @RocketHazmat: Opera does for example – Bergi Oct 06 '14 at 19:46
  • 1
    Lets rewrite your code as `function action(number) { sum += number; } ; for (var i = 0; i < array.length; i++) action(array[i]);`. Does this code make sense to you? It's basically the same as you have, just that you abstract the `for` loop into the function `forEach`. – Felix Kling Oct 06 '14 at 19:51
  • Related: [previous question about the syntax](http://stackoverflow.com/q/26219919/1048572) – Bergi Oct 06 '14 at 19:56

4 Answers4

1

at what point does the function know about every element

At no point (just like when you pass in console.log).

The forEach function calls it on this line:

action(array[i]);

At which point, it only knows about a single value from the array because that is all that is passed into it.

(It also knows about the sum variable because that is defined in a wider scope than the function).

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
1

How does it get evaluated?

It creates a new scope (with array, action and i variables) and assigns the function to the action variable - that's a function invocation.

Your

var sum = 0;
forEach([1, 2, 3, 4, 5], function(number) {
  sum += number;
});

is just the same as

var sum = 0;
{ // let's assume block scope here
  var array = [1, 2, 3, 4, 5],
      action = function(number) {
        sum += number;
      },
      i;
  for (i = 0; i < array.length; i++)
    action(array[i]);
}

If I pass console.log into the last problem and it still has to be evaluated, then doesn't that apply to the entire function too since that function is the parameter?

Yes, exactly. You are passing a function object - and whether get that by referencing the console.log variable or by creating it on the fly (with the function expression) doesn't matter to forEach. It only will get executed with action(…) - where the array[i] value is passed for the number parameter of your function.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

In JavaScript, functions are first-class citizens. That means they can be treated as variables, just like strings, numbers, etc.

When you do:

forEach(numbers, function(number) {
  sum += number;
});

You are passing forEach an anonymous function. Instead of the function being in a variable, it's created on-the-fly. Inside your forEach function, action will contain your anonymous function.

In for for loop, the action function is called for each element.

gen_Eric
  • 223,194
  • 41
  • 299
  • 337
  • so it is really? is that what you are saying? var action = function(number){ sum += number; } – Stack Overflow 421 Oct 06 '14 at 19:55
  • @StackOverflow421: Yeah, basically. The `action` in your `forEach` will be set to the function you passed as a parameter. Functions are just objects. – gen_Eric Oct 06 '14 at 19:55
  • Ok and when that function is passed in, when does number turn into array([i]) see fiddle please http://jsfiddle.net/zq5trh30/ – Stack Overflow 421 Oct 06 '14 at 20:01
  • @StackOverflow421: `action` is a *function*. The contents of the function don't matter until it's *ran*. When you do `action(array[i]);`, you are *calling* a function. That's when `number` gets set; when you *call* the function. – gen_Eric Oct 06 '14 at 20:03
  • I'm really confused on this, do you mind showing me on stypi possibly? for a quick second I just don't understand this concept. – Stack Overflow 421 Oct 06 '14 at 20:07
  • @StackOverflow421: What's stypi? It is a little confusing... I know. Just try to think that, in JavaScript, functions are just variables (objects) like strings, numbers, arrays, etc. You pass functions like you would an array. Think of what you are doing here as a "function literal" (like an "array literal" or "string literal"). The `function(number)` is just treated as a variable. When you do `action(array[i]);`, you are *calling* that function, so it gets ran with `number` set to `array[i]`. That's just like `console.log`. – gen_Eric Oct 06 '14 at 20:36
0

Heres the solution to your problems:

    function forEach(array, action) {
        for (var i = 0; i < array.length; i++){
            if(typeof action == 'function'){
                action(array[i]);
            }else{
                var slices = action.match(/(.+)\.([a-zA-Z0-9]+)/);
                var object = eval(slices[1]);
                var action = slices[2];
                object[action](array[i]);
            }
        }
    }

I've tested it with both scenarios and it works like magic

enter image description here

enter image description here

Derick Fynn
  • 395
  • 3
  • 6