example to illustrate variable values in different scopes, in both assignment and re-assignment cases.
... html here ...
<script>
// Scope chain example, showing lexical and variable environments in a call tree.
// "this" is always window
// [name] is execution context. E.g., "win" is the window execution context.
// "[name]Vars" corresponds to [name]'s variable environment (function scoped, identifiers defined with var, and function args)
// "[name]Lexs" corresponds to [name]'s lexical environment (block scoped, identifiers defined with const and let)
// "outer" property corresponds to the closest parent environment
// assign some variables in the window execution environment
winGlobal=0; // since we're in the window execution context, including and excluding "var" keyword operate identically
var winVar=0;
let winLet=0;
const winConst=0;
// winVars:{winGlobal:0,winVar:0,foo:fn,outer:undefined},winLexs:{winLet:0,winConst:0,outer:winVars}
// foo has not yet executed
function foo(fooArg=0){
fooGlobal=0 // fooLexs.fooGlobal missing. fooVars.fooGlobal missing. winLexs.fooGlobal missing. winVars.fooGlobal missing, but no outer to follow, so assign winVars.fooGlobal=0.
var fooVar=0; // defined with var, so assign to closest variable environment (fooVars)
const fooConst=0; // defined with const, so assign to closest lexical environment (fooLexs)
let fooLet=0; // defined with let, so assign to closest lexical environment (fooLexs)
// winVars:{winGlobal:0,winVar:0,fooGlobal:0,outer:undefined} winLexs:{winLet:0,winConst:0,outer:winVars}
// fooVars:{fooArg:0,fooVar:0,outer:windowLexs} fooLexs:{fooConst:0,fooLet:0,outer:fooVars}
// reassign. Without using keywords like let/const/var, will reassign values on closest env it's assigned
winGlobal=1;
winVar=1;
winLet=1;
winConst; // can't reassign const
fooGlobal=1;
fooArg=1;
fooVar=1;
fooLet=1;
fooConst; // can't reassign const
// winVars:{winGlobal:1,winVar:1,fooGlobal:1,outer:undefined} winLexs:{winLet:1,winConst:0,outer:winVars}
// fooVars:{fooArg:1,fooVar:1,outer:winLexs} fooLexs:{fooLet:1,fooConst:0,outer:fooVars}
if(true){ // braces like this create a nested lexical Environment
ifGlobal = 0;
var ifVar = 0;
const ifConst = 0;
let ifLet = 0;
// winVars:{winGlobal:1,winVar:1,foo:fn,fooGlobal:1,ifGlobal:0,outer:undefined} winLexs:{winLet:1,winConst:0,outer:winVars}
// fooVars:{fooArg:1,fooVar:1,ifVar:0,outer:winLexs} fooLexs:{fooLet:1,fooConst:0,outer:fooVars}
// ifLexs: {ifLet:0,ifConst:0,outer:fooVars} (no ifVars. Block scopes (braces) only have their own lexical environment, no variable environment)
// reassign. Without using keywords like let/const/var, will reassign values on closest env it's assigned
winGlobal=2;
winVar=2;
winLet=2;
winConst;
fooGlobal=2;
fooArg=2;
fooVar=2;
fooLet=2;
fooConst;
ifGlobal = 2;
ifVar = 2;
ifLet=2;
ifConst;
// winVars:{winGlobal:2,winVar:2,foo:fn,fooGlobal:2,ifGlobal:2,outer:undefined} winLexs:{winLet:2,winConst:0,outer:winVars}
// fooVars:{fooArg:2,fooVar:2,ifVar:2,bar:fn,outer:winLexs} fooLexs:{fooLet:2,fooConst:0,outer:fooVars}
// ifLexs: {ifLet:2,ifConst:0,outer:fooVars}
const bar = function(barArg=0){
barGlobal = 0;
var barVar = 0;
const barConst = 0;
let barLet = 0;
// winVars:{winGlobal:2,winVar:2,foo:fn,fooGlobal:2,ifGlobal:2,barGlobal:0,outer:undefined} winLexs:{winLet:2,winConst:0,outer:winVars}
// fooVars:{fooArg:2,fooVar:2,ifVar:2,bar:fn,outer:winLexs} fooLexs:{fooLet:2,fooConst:0,outer:fooVars}
// ifLexs: {ifLet:2,ifConst:0,bar:fn,outer:fooVars}
// barVars:{barArg:0,barVar:0,outer:ifLexs} barLexs:{barLet:0,barConst:0,outer:barVars}
// reassign. Without using keywords like let/const/var, will reassign values on closest env it's assigned
winGlobal=3;
winVar=3;
winLet=3;
winConst;
fooGlobal=3;
fooArg=3;
fooVar=3;
fooLet=3;
fooConst;
ifGlobal=3;
ifVar=3;
ifLet=3;
ifConst;
barGlobal=3;
barArg=3;
barVar=3;
barLet=3; // reassign wherever it's most closely assigned
barConst; // can't reassign const
// bar() has executed
// winVars:{winGlobal:3,winVar:3,foo:fn,fooGlobal:3,ifGlobal:3,barGlobal:3,outer:undefined} winLexs:{winLet:3,winConst:0,outer:winVars}
// fooVars:{fooArg:3,fooVar:3,ifVar:3,bar:fn,outer:winLexs} fooLexs:{fooLet:3,fooConst:0,outer:fooVars}
// ifLexs: {ifLet:3,ifConst:0,bar:fn,outer:fooVars} (no ifVars. Block scopes (braces) only have their own lexical environment, no variable environment)
// barVars:{barArg:3,barVar:3,outer:ifLexs} barLexs:{barLet:3,barConst:0,outer:barVars}
}
bar();
}
// bar() and foo() have executed
// winVars:{winGlobal:3,winVar:3,foo:fn,fooGlobal:3,ifGlobal:3,barGlobal:3,outer:undefined} winLexs:{winLet:3,winConst:0,outer:winVars}
// fooVars:{fooArg:3,fooVar:3,ifVar:3,bar:fn,outer:winLexs} fooLexs:{fooLet:3,fooConst:0,outer:fooVars}
// ifLexs: undefined since we're outside the if(true){} block scope
// barVars: undefined since we're outside bar's function scope
}
foo();
// bar() and foo() have executed
// winVars:{winGlobal:3,winVar:3,foo:fn,fooGlobal:3,ifGlobal:3,barGlobal:3,outer:undefined} winLexs:{winLet:3,winConst:0,outer:winVars}
// fooVars/fooLexs: undefined since we're outside foo's function scope
// ifLexs: undefined since we're outside the if(true){} block scope
// barVars/barLexs: undefined since we're outside bar's function scope
</script>