2
var foo = function bar(i) {
    bar = "change bar reference";
    if (i < 5) {
        setTimeout(function () {
            console.log(i);
            bar(++i);
        },
            1000
        );
    }
}

No errors for the above function.

var foo = function bar(i) {
    var bar = "change bar reference";
    if (i < 5) {
        setTimeout(function () {
            console.log(i);
            bar(++i);
        },
            1000
        );
    }
}

Error on the second function after adding in var to bar.

I expected both of the functions to throw an exception not just the second function with var bar.

I don't understand why only the second function throws an exception.

I get that the variable declaration with var will not overwrite the identifier "bar" but the assignment will do it at run time. I understand why var bar is a string not a function on the second function therefore throws an exception.

Why doesn't the first function throw exception? bar is clearly assigned to a string.

I read the documentation and got something below that might be useful.

The Identifier in a FunctionExpression can be referenced from inside the FunctionExpression's FunctionBody to allow the function to call itself recursively. However, unlike in a FunctionDeclaration, the Identifier in a FunctionExpression cannot be referenced from and does not affect the scope enclosing the FunctionExpression.

Does "the Identifier in a FunctionExpression cannot be referenced from" mean I can not do bar = "change bar reference"; in the 1st function?

What does the JavaScript script engine do when it sees bar = "change bar reference"? Does it just skip the line?

Edit: Uncaught TypeError: bar is not a function

foo(1)

bluejimmy
  • 390
  • 6
  • 14
  • Exactly what "exception" is it that you get? – Pointy Jun 21 '18 at 13:10
  • you are changing function `bar` to a string, thats why you see the error. – Inus Saha Jun 21 '18 at 13:11
  • 2
    The first one, your setting `bar` on the global scope,.. But you also have `bar` in local scope, and that will been seen first.. In the second one your creating both in local scope. If you changed the first one to say `window.bar` it will become more obvious what you have done. – Keith Jun 21 '18 at 13:12
  • @Pointy Uncaught TypeError: bar is not a function. – bluejimmy Jun 21 '18 at 13:21
  • @Keith Im pretty sure that the first function does not have `bar` on global scope. `bar` is the identifier from the named function expression which can only be accessed inside the function. That is what make the problem interesting. – bluejimmy Jun 21 '18 at 13:55
  • @ Inus Saha I know. But both functions try to change `bar` to a string but why only one throws exception? That is because one is a constant and the other is not for some reason. For function 1, `bar` is a constant, and for function 2, bar is not a constant. – bluejimmy Jun 21 '18 at 14:06
  • @bluejimmy I've added some spec references to my answer that hopefully illuminate the matter a bit. – JLRishe Jun 21 '18 at 15:28
  • Related: [Variable in function body and function itself have the same name (JavaScript)](https://stackoverflow.com/q/24021489/4642212). – Sebastian Simon Aug 17 '19 at 09:21

3 Answers3

2

The reason for this is that as per the ES spec, there is an immutable binding on the named function's name:

From Section 14.1.20 of the ES6 spec:

  1. Let envRec be funcEnv’s EnvironmentRecord.
  2. Let name be StringValue of BindingIdentifier.
  3. Perform envRec.CreateImmutableBinding(name).

An immutable binding means that the identifier's value cannot be overwritten. Additionally, attempting to assign to it in strict mode should produce a runtime error:

12.14.1: In ECMAScript 2015, strict mode code containing an assignment to an immutable binding such as the function name of a FunctionExpression does not produce an early error. Instead it produces a runtime error.

Observe:

var foo = function bar() {
    bar = 2;
    console.log(bar);
};

foo();

So in your first example, your assignment to bar essentially has no effect unless it is in strict mode.

In the second one, you are shadowing the bar identifier by declaring a variable in a new scope. That's why bar retains the assigned value in the second example.

var foo = function bar() {
  var bar = 2;
  console.log(bar);
};

foo();
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • 1
    I suspect that the assignment to `bar` *does* have an effect, that being the implicit creation of a global `bar`. It would probably throw an error if the function were in "strict" mode. – Pointy Jun 21 '18 at 13:25
  • 2
    @Pointy I thought that it would create a global variable as well but it doesnt. Ran it as strict mode and it gave me "TypeError: Assignment to constant variable". So i guess bar identifier is a constant value that cant be changed and it doesnt throw error in non strict mode. – bluejimmy Jun 21 '18 at 13:34
  • Ah OK, so it ends up behaving like a `const` symbol. I did not expect that but it's interesting. – Pointy Jun 21 '18 at 13:34
  • it is a ploblem of the used browser, in edge, nothing happnes, no error/exception. – Nina Scholz Jun 21 '18 at 13:37
  • Indeed, interesting.. JS is such a fun language sometimes.. :) – Keith Jun 21 '18 at 13:38
  • @Pointy it is weird that a named function expression identifier `bar` can be a constant or a regular variable depending on what is inside the function body. If you put `var bar` then `bar` identifier acts like a regular variable. – bluejimmy Jun 21 '18 at 13:48
  • This is fascinating behavior. I assume that it works as specified, but it's quite odd. – Scott Sauyet Jun 21 '18 at 14:12
  • @NinaScholz According to the line I've now quoted above, it seems like it's supposed to throw an error, but I'm a bit confused because that seems to be in contradiction with the line numbered "6" above doesn't pass **true** as the second argument to `CreateImmutableBinding`. Or maybe I'm missing something. – JLRishe Jun 21 '18 at 15:13
  • @bluejimmy My understanding of the spec-ese is a bit shaky, but as I said in my answer, I'm pretty sure that what's happening is that `var bar` creates _a new variable in a new scope_. So it's not making the original `bar` writeable; it's [shadowing](https://simpletutorials.com/c/2239/Variable+Shadowing+in+JavaScript) it with a new variable. – JLRishe Jun 21 '18 at 15:24
  • @JLRishe What scope is `bar` in? I know that `bar` is not in global scope so it must be local scope. If it is in local scope then how you declare `bar` in a new scope? It is strange that `var bar` is shadowing bar but I am not sure where `bar` is located. – bluejimmy Jun 21 '18 at 15:28
  • @bluejimmy It's scoped to the function's [environment record](https://www.ecma-international.org/ecma-262/6.0/#sec-function-environment-records). If my understanding is correct, a function environment record is a special kind of scope that exists on a level immediately above the scope inside the function body. – JLRishe Jun 21 '18 at 15:31
  • 1
    @JLRishe Ok, I finally understand everything about the problem. I can go to sleep now. Thank you. – bluejimmy Jun 21 '18 at 15:36
1

At least when calling foo or bar, you get an error because the function does not exists anymore.

Why doesn't the first function throw exception? bar is clearly assigned to a string.

At runtime, without calling the function, nothing happens, but after the call of the function, the variable bar has a string as value, not a function for further calling for the timeout.

It depends. On Edge, it just skips the assignment, on Chrome it throws an error

Assignment to constant variable.

which means named function are implemented as const.

var foo = function bar(i) { // <<<---------+
        bar = "change bar reference"; // --+ tries to change global bar
        if (i < 5) {
            setTimeout(
                function () {
                    console.log(i);
                    bar(++i);
                },
                1000
            );
        }
    };

foo(0);

I read the documentation and got something below that might be useful. "The Identifier in a FunctionExpression can be referenced from inside the FunctionExpression's FunctionBody to allow the function to call itself recursively. However, unlike in a FunctionDeclaration, the Identifier in a FunctionExpression cannot be referenced from and does not affect the scope enclosing the FunctionExpression."

Does "the Identifier in a FunctionExpression cannot be referenced from" mean I can not do bar = "change bar reference"; in the 1st function?

Your question is unclear.

What does the JavaScript script engine do when it sees bar = "change bar reference"? Does it just skip the line?

No. It assign the value to either the local bar or the global bar, but if having a local or global variable the the local variable is used over the global one.

'use strict';
function foo() {
    var a = 'bar';
    console.log(a); // bar
}

var a = 42;

foo();
console.log(a); // 42
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Sorry, i had some typos. Try to copy and paste again. It should work. foo(1) – bluejimmy Jun 21 '18 at 13:22
  • Named function expressions are only constant when there is no variable declaration inside the function with the same identifier as I just found out. If you look at the second function where there is `var bar`, the `bar` acts like a regular variable. All my tests are on chrome. – bluejimmy Jun 21 '18 at 13:58
  • but in the second it uses bar with the string as function, which throws an error. – Nina Scholz Jun 21 '18 at 14:00
  • Both of the the functions try to change `bar` to a string. Only one is successful because the other one is a constant. Both of the functions are the same except one with an extra `var`. – bluejimmy Jun 21 '18 at 14:03
0

Let me try to elaborate in simple word. In first function, you are trying to modify/redefine/override NFE (Named Function Expression) “bar” that is not permissible. As you have not used “strict mode” that’s why you did not receive run time error. In case you will ON strict mode, you will receive “Uncaught TypeError: Assignment to constant variable” because inside NFE function name treated like a constant so that nobody can override its value.

In second function, “bar” will infer a string value that’s why it throws error because after few line, you are using it as a function. I hope, it make some sense. To double check, we can execute below script: Below script will return “function” and then definition of “bar” function because I have not used strict mode

var foo = function bar() {
console.log(typeof(bar)) ;
bar = "new definition"; // try to redefine
console.log( bar ); 
};