2

MDN says that "A function declaration is also limited in scope inside the block where the declaration occurs" (link) and shows an example:

foo('outside');  // TypeError: foo is not a function
{
  function foo(location) {
   console.log('foo is called ' + location);
  }
  foo('inside'); // works correctly and logs 'foo is called inside' 
}

So far so good, but if we call function under its declaration:

{
  function foo(location) {
   console.log('foo is called ' + location);
  }
  foo('inside'); // works correctly and logs 'foo is called inside' 
}
foo('outside');  // 'foo is called outside' 

Suddenly function is no longer block scope, but rather function scope. It behaves exactly like var, which is function scope:

console.log(typeof foo);//undefined
{
    var foo = function(location) {
        console.log('foo is called ' + location);
    }
    console.log(typeof foo);//function
}
console.log(typeof foo);//function

My question is: what is the scope of function? And if its scope is function what's wrong with hoisting? Or is it correct implementation, but I don't understand something correctly?

Because of some confusion I have created below examples: 1 everything is fine

foo();//logs 'bar' because of hoisting
function foo(){
        console.log('bar');
}

1b everything is fine, function expressions aren't hoisted

foo();//Error! foo is declared, so it was hoisted, but without its body, so it cannot be called
var foo = function(){
    console.log('bar');
}

2 This one causes confusion. If functions scope is function, then why below function declaration is not hoisted? For some strange reason it is working like 1b (function expression, not function declaration)

foo();//Error! foo is declared, so it was hoisted, but without its body, so it cannot be called
{
    function foo(){
        console.log('bar');
    }
}

3 If function scope is block, then why function is available outside of the block?

{
    function foo(){
        console.log('bar');
    }
}
foo();//logs 'bar'

3b everything is fine. Let scope is block, so it's unavailable outside of block

{
    let foo = function(){
        console.log('bar');
    }
}
foo();//Error, not declared
PatrykD
  • 71
  • 1
  • 10
  • 1
    Ok, you've found a flaw in MDN (which is a public wiki that anyone can contribute to, it's not a formal specification or normative). Declarations (function and variable) are limited by execution contexts, which are created when a function is called. Functions are created by the function keyword followed by a block containing zero or more statements. That is the type of block that limits the scope of declared variables and functions, not **any** block (like *for*, *if*, etc.). – RobG Feb 22 '18 at 10:08
  • I was trying to find it in ECMAScript specification, but didn't manage (it's still a little bit too complicated for me, that's why I am refering MDN). Still, assuming that functions scope is function, not block, why in my first example hoisting is not working? If we remove block (code below) everything is fine. I am wondering if this is intended or the behaviour is different than in specification. `foo('outside'); // Ok now function foo(location) { console.log('foo is called ' + location); } foo('inside'); // works correctly and logs 'foo is called inside'` – PatrykD Feb 22 '18 at 11:12
  • 1
    Function declarations inside blocks have _undefined behavior_. Avoid them. – JLRishe Feb 22 '18 at 11:36
  • @JLRishe thank you for answer and hinting to another stack question. I have found link there (http://kangax.github.io/nfe/#function-declarations-in-blocks) that explains topic very well. I will correct mdn article at the weekend. – PatrykD Feb 22 '18 at 12:04

1 Answers1

1

function declaration is function-scoped only, as demonstrated below

function test() {
  function a() {
    console.log("outer a");
  }
  if (1) {
    function a() {
      console.log("inner a");
    }
    function b() {
      console.log("inner b");
    }
    a(); //will call inner a
  }
  a(); //will call inner a
  b(); //will call inner b
}
test();

Output

inner a

inner a

inner b

Edit - Scenario - Inner function declaration not hoisting value to top

Also, it seems that function declaration is function-scoped, its hoisting (of value) is only happening inside the block, which is why b (second statement in function) prints undefined, while a prints as a function.

function test() {
  console.log(a); //prints outer a
  console.log(b); //undefined 
  function a() {
    console.log("outer a");
  }
  if (1) {
    function a() {
      console.log("inner a");
    }

    function b() {
      console.log("inner b");
    }
    a(); //will call inner a
  }
  a(); //will call inner a
  b(); //will call inner b
}
test();

Output

function a() { console.log("outer a"); }

undefined

inner a

inner a

inner b

gurvinder372
  • 66,980
  • 10
  • 72
  • 94
  • But this clearly isn't what's happening [as demonstrated by the OP's example](https://jsfiddle.net/qmd7y7o2/2/). – Andy Feb 22 '18 at 10:00
  • `foo` has been hoisted, its value isn't. Check that its value is `undefined`. https://jsfiddle.net/qmd7y7o2/4/ – gurvinder372 Feb 22 '18 at 10:02
  • This is quite confusing indeed. The documentation clearly says that the function declaration is block scoped, but your example shows that it's function scoped. And the reason why the OP's example doesnt work is that it seems that the function is hoisted at the top of the scope it's declared in. – Logar Feb 22 '18 at 10:03
  • 1
    @Logar There is no confusion. Hoisting is only about scope, not the value. foo is hoisted, its value isn't. – gurvinder372 Feb 22 '18 at 10:05
  • This makes sense now, thank you for your last edit – Logar Feb 22 '18 at 10:07
  • It was my understanding that function _declarations_ were wholly hoisted, not just the value, unlike function _expressions_, where it _is_ just the value which is why I surprised the OPs example didn't work. – Andy Feb 22 '18 at 10:10
  • I see @RobG has answered my question :) – Andy Feb 22 '18 at 10:12
  • @Logar—please don't get hung up on MDN, it's not normative. Please ignore "hoisting". Declarations are processed before any code is executed, a function declaration includes the parameters and body inside its block of statements, a variable declaration consists only of `var` plus an identifier. The following assignment (if present) is only executed (i.e. the value assigned) in sequence after all declarations have been processed. – RobG Feb 22 '18 at 10:13
  • @Andy function declarations are wholly hoisted (with body) as long as they are not in block. In fact many developers consider putting function declarations at the bottom of the code good practice, because its easier to see what code actually does (you don't have to scroll in search for first call). That's why the whole situation is confusing for me. – PatrykD Feb 22 '18 at 11:16
  • @PatrykD `var foo = function(){}` is not same as `function foo(){}` for the exact same reason. https://stackoverflow.com/questions/336859/var-functionname-function-vs-function-functionname?rq=1 – gurvinder372 Feb 22 '18 at 11:17
  • @gurvinder372 I know and understand it very well, but my point is why `{ function foo(){} }` is not hoisted like `function foo(){}`? – PatrykD Feb 22 '18 at 11:19
  • @PatrykD Understood now, let me check the spec. It seems that though function declaration is function-scoped, its hoisting (of value) is only happening inside the block. – gurvinder372 Feb 22 '18 at 11:28
  • @gurvinder372— 'cos `var foo = function(){}` isn't a function declaration, it's a variable declaration with initialiser, where the value is crated by a function expression. – RobG Feb 22 '18 at 12:20
  • @RobG Correct, did I mentioned otherwise in my post or comments? – gurvinder372 Feb 22 '18 at 12:21
  • @gurvinder372—no, but the question is about function declarations. – RobG Feb 22 '18 at 12:23
  • @RobG It has both actually, check the *2)* part – gurvinder372 Feb 22 '18 at 12:23
  • @gurvinder372—ok, but 2) is a function expression because of the leading `{`, whereas the comment is because it's used as an initialiser. Anyhow, I think the OP has it. – RobG Feb 22 '18 at 12:32