All the answers are great. I have some time on my hands this morning and thought I would add to the great answers above and explain a little further what is happening under the hood.
‘use strict’;
Using ‘let’ in this case, and not ‘var’, will scope to the environment during the execution context. What I mean by that is, for every execution (invocation, run) of any function, the JS Engine (for instance, Google’s V8 JS Engine in Chrome) will create a ‘new function execution context’ ; a stand-alone environment (“a private box”, for an anology) where the engine (the JavaScript interpretor) will work only on that function until done.
This is something I recommend looking into and learning. It makes writing and understanding code so much easier.
let printNumTwo;
// saved in the ‘heap’ : global memory.
The code statement above: we declare a variable without instantiation to use as the variable in the function expression, within the for…loop code block.
printNumTwo = function {
// This is the ‘function expression’ . We assign an anonymous function to a variable: ‘printNumTwo’.
Using ‘let i = 0’ in the [ for statement ] scopes ’ i 'only to the for-loop. The variable ’ i ’ of the for-loop is ‘scoped’ (visible) to the for-loop iteration operation. The function’s “lexical” environment, (meaning it belongs to the for-loop neighborhood) can access this ‘i’ in memory.
The console.log(i) method cannot.
for (let i = 0; i < 3; i++) {
When the interpreter reads the script (the file.js) file the first time through, it only reads the code, line-by-line, and allocates (makes space at the machine level for bits and bytes) for the variable and function name’s (indentifier’s) and respective definitions, and so forth. It sets up the code to be worked on later. (And it does a lot of other things too.)
When the JS engine reads the "console.log(printNumTwo( ) ) ", the second time around, it reads the log(), then goes inside it, and reads the printNumTwo(), and because of the parentheses, it will “execute” the function, and not just return a function defintion body. SO, it goes and looks for that specific name in memory. In memory, it see’s that it was previously assigned an anonymous function ( “a function without a name”). It will then find that anonymous function’s, function definition, and together, the JS engine will now create an execution context (remember that private box I mentioned before).
Now, this is important. In this execution function context, the JS engine reads the return i ; statement within the execution context, and looks for ‘i’ inside the function body. Not there! Then it will look up to the for…loop conditional. It finds ’ i ’ there in the memory heap of the for-loop iteration. The function has access to the for-loop, and thus the variable, ‘i’.
When executing console.log(i); - the JS engine will not find that ’ i '. The function printNumTwo(){ } has closed in (closure) the ‘i’ to the function operation only. It has hidden it from the simple log() operation.
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo()); // returns 2
console.log(i); // returns “i is not defined”