William M.
William M.

Reputation: 13

Understanding anonymous JavaScript function definition with regard to closures

I have a good handle on closures conceptually, but a problem has arisen that I can't quite understand.

When creating a function to pass some value to an inner function without binding it to the outermost function's final value when it returns, this doesn't appear to be allowed:

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
            function (x) {  // this anonymous function definition...
                var item = 'item' + list[x];
                result.push(
                    return function () {
                        console.log(item + ' ' + list[x]);
                    };
                );
            }(i);           // and simultaneous invocation...
    }
    return result;
}

Whereas if I move the closure completely inside the call to .push(), everything works out fine:

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
            result.push(
                function (x) {  // wrapper now inside the call to .push()
                    var item = 'item' + list[x];
                    return function () {
                        console.log(item + ' ' + list[x]);
                    };
                }(i)           // and called here...
            );
    }
    return result;
}

What I'm wondering is: what rule am I violating when I define the anonymous function that wraps the closure immediately inside the for loop as opposed to inside the call to .push()?

Upvotes: 0

Views: 44

Answers (3)

Vivin Paliath
Vivin Paliath

Reputation: 95518

By "not allowed", I'm assuming that the interpreter is complaining about a syntax error. What you have in the first case:

result.push(
    return function () {
        console.log(item + ' ' + list[x]);
    };
);

Isn't syntactically valid.

But even if you remove the return:

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
            function (x) {  // this anonymous function definition...
                var item = 'item' + list[x];
                result.push(
                    function () {
                        console.log(item + ' ' + list[x]);
                    }
                );
            }(i);           // and simultaneous invocation...
    }
}

You will still get an error. This is because you haven't specified the parentheses to the IIFE, which means function (x) { ... }() is treated as a declaration/statement regardless of the trailing (). But if you are declaring a function, you need to specify a name, which is why the ( after function is unexpected. If you want to treat it as an expression, you have to wrap it in (...) and hence use (function (x) { ... })().

In the second case, the argument to result.push(...) can only be an expression, so there is no ambiguity as to how function (x) { ... }() is to be interpreted; it can never be a declaration, and so it has to be an expression (either a function-literal or an IIFE).

As an analogy, think of function() { ... } like the string "hello". You can never use "hello" in isolation; the following code isn't syntactically valid:

var x = "foo";
"hello";

This is essentially what you are doing with the anonymous function:

var x = "foo";
function () {
}

What should be done with that function? It isn't not assigned to anything, just like "hello" in the earlier example isn't assigned to anything. But we know that functions can be invoked, so what we are doing with (function() { ... } ()) is saying "take this function I have defined here and then call it right now". It is analogous to calling a method on a string literal:

"abcd".substring(0, 2); // returns "ab"

And indeed, you could do something similar with a function, which I think demonstrates a little better what is happening with the IIFE:

// logs "hello"
(function() { 
    console.log("hello");
}).call();

The parentheses is a way to remove ambiguity and tell the interpreter that you want to treat the function as an expression/literal instead of a declaration. In the above example, if you removed the surrounding parentheses, you would get the same syntax error about the unexpected (.

Upvotes: 1

Bhabishya Kumar
Bhabishya Kumar

Reputation: 731

In the first case, you are pushing return of your function declaration NOT invocation, which is effectively

result.push(return function(){...})

In the second case, you are pushing the return of IIFE function execution, which returns your original function, so its effectively

result.push(function(){...})

Obviously, second is what you want. You can change first to

result.push(
    function () {
        console.log(item + ' ' + list[x]);
    };
);

to make it work. No return while pushing.

Upvotes: 0

VRPF
VRPF

Reputation: 3118

There was a syntax error with the wrapper (IIFE) function and wrongly placed return statement in the first case. Here a fixed (and slightly modified) snippet.

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
            (function (x) {  // this anonymous function declaration...
                var item = 'item' + list[x];
                result.push(
                    function () {
                        console.log(item + ' ' + list[x]);
                        return item + ' ' + list[x];
                    }
                );
            })(i);           // and simultaneous invocation...
    }
    return result;
}

buildList([1,2,3]).forEach(function(func) { 
  
  document.body.innerHTML += func() + '<br>';

});

Upvotes: 0

Related Questions