Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inner function scope in Javascript

In THIS article, I do not understand the following statement:

Note that the inner function cannot call the outer function’s arguments object, however, even though it can call the outer function’s parameters directly.

Can someone please help me understand both the points mentioned

  1. Inner function cannot call the outer function’s arguments object
  2. Inner function can call the outer function’s parameters directly.
like image 962
Andy897 Avatar asked Mar 23 '15 17:03

Andy897


1 Answers

The keys to understanding both of those statements are:

  1. Understanding what arguments is

  2. Understanding "shadowing"

So let's talk about arguments, then talk about shadowing, and then I think it'll be fairly clear what those two statements are trying to say.

arguments

The arguments object is provided as part of the scope within a function: It's effectively an automatically-defined variable that has a pseudo-array of the arguments the function was called with:

function foo() {
    console.log("My arg count: " + arguments.length);
}
foo(1, 2, 3); // My arg count: 3

When I say automatically-defined, I mean it's very much as though you had this:

// CONCEPTUAL, not real
function foo() {
    var arguments = /*...magic code to get the arguments we were called with */;
    console.log("My arg count: " + arguments.length);
}
foo(1, 2, 3); // My arg count: 3

...it's just that the JavaScript engine does it for you.

The arguments object is a lot like an array — as we saw above, it has length, and you can access the individual arguments using the usual [] notation:

function foo() {
    var n;
    console.log("My arg count: " + arguments.length);
    for (n = 0; n < arguments.length; ++n) {
        console.log(arguments[n]);
    }
}
foo(1, 2, 3);

Output:

My arg count: 3
1
2
3

You can even use named arguments and the arguments pseudo-array in the same function:

function foo(a) {
    console.log("a = " + a);
    console.log("arguments[0] = " + arguments[0]);
}
foo("Hi there");

Output:

a = Hi there
arguments[0] = Hi there

It's a pseudo-array because although it has length and [] indexing, it's not really an array and doesn't have all of the features normal arrays have (like slice, forEach, etc.).

In loose mode, it also has the very surprising property that it's linked to the named arguments, so you get weird stuff like this:

function foo(a) {
    console.log("before: a = " + a);
    arguments[0] = a * 2;
    console.log("after:  a = " + a);
}
foo(10);

Output:

befer: a = 10
after: a = 20

Spooky, eh? In strict mode, that link isn't there (because it's expensive for the engine to do that).

Okay, so that's arguments. What's this "shadowing" thing?

Shadowing

Functions create a "scope" for variables and such to exist in, and since you can have functions inside other functions, you can have scopes inside scopes. Functions inside other functions have access to the scopes that contain them, so:

function outer() {
    var a = 10;

    // `a` exists here

    function inner() {
        var b = 20;

        // both `a` and `b` exist here

        console.log("a = " + a + ", b = " + b);
    }

    // only `a` exists here

    inner();
}

a exists in the scope of outer, which inner has access to. b only exists within inner.

That's fine as long as we use different names, but suppose we used a for both of them:

function outer() {
    var a = 10;

    // `a` exists here

    function inner() {
        var a = 20;

        // `a` exists here -- but which one is it?

        console.log("a = " + a);
    }

    inner();
}

There, since inner has its own a variable, it can't access outer's a anymore — inner's a shadows (hides) outer's a.

This is true for variables, named arguments, functions created within a scope, anything that gives a name a meaning within a scope.

So what does shadowing have to do with the two statements? Let's find out...

"1. Inner function cannot call the outer function’s arguments object"

"Call" is the wrong word here; "access" would make more sense.

Remember our conceptual example above, where arguments is basically an automatically-declared variable?

// CONCEPTUAL, not real
function foo() {
    var arguments = /*...magic code to get the arguments we were called with */;
    console.log("My arg count: " + arguments.length);
}
foo(1, 2, 3); // My arg count: 3

Now that you know about shadowing, you can probably see where this is going. This code:

function outer() {
    function inner() {
        console.log("Inner arg count: " + arguments.length);
    }
    console.log("Outer arg count: " + arguments.length);
    inner();
}
outer(1, 2, 3);

shows

Outer arg count: 3
Inner arg count: 0

...because inside inner, arguments refers to the arguments object for the inner function, not the outer one. It's exactly like they both declared a local variable with the same name: The variable in inner shadows (hides) the one in outer.

Now, "Inner function cannot call the outer function’s arguments object" isn't really true. The inner function can access the outer function's arguments object, but not by that name — if outer assigns it to a variable, though, there's no problem:

function outer() {
    var outerArgs = arguments;
    function inner() {
        console.log("Outer's arg count from inner: " + outerArgs.length);
    }
    console.log("Outer arg count: " + arguments.length);
    inner();
}
outer(1, 2, 3);

shows

Outer arg count: 3
Outer's arg count from inner: 3

"2. Inner function can call the outer function’s parameters directly."

This statement is incomplete: inner can access outer's arguments provided inner doesn't shadow them. So this is fine:

function outer(a) {
    function inner() {
        console.log("Outer's a = " + a);
    }
    inner();
}
outer(10);

Output:

Outer's a = 10

...but here, a gets shadowed and so inner only sees its own a, not outer's:

function outer(a) {            // <== declares a named argument `a`
    function inner(a) {        // <== ALSO declares a named argument `a`
        console.log("a = " + a);
    }
    inner();
}
outer(10);

Output:

a = undefined

So really, both statements are just about scope and shadowing.

like image 57
T.J. Crowder Avatar answered Oct 23 '22 03:10

T.J. Crowder