Block-level variable redeclaration with var vs. let/const - javascript

Part 1
Given this example:
var number = 10
{
var number = 42
}
console.log(number) // 42
Why does line 4 not throw an Uncaught SyntaxError: Identifier 'number' has already been declared? It works with let / const because of block scoping (although the output is, of course, 10 not 42), but how come it works with var?
Part 2
Compare this to the following, which works with var:
var number = 10
var number = 42
console.log(number) // 42
but doesn't with let:
let number = 10
let number = 42 // SyntaxError
console.log(number)
Why is var given a "free pass"? Does it have to do with the number property being reassigned to the window object when var is used?

You are allowed to redeclare var variables in JavaScript, even in strict mode.
The scope of a variable declared with var is its current execution context, which is either the enclosing function or, for variables declared outside any function, global. If you re-declare a JavaScript variable, it will not lose its value.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting
'use strict'
var someVar = 'Hello';
var someVar = 2 + 2;
console.log(someVar);

Why does line 4 not throw an Uncaught SyntaxError: Identifier 'number' has already been declared?
As Sébastien already mentioned, variables declared with var are declared in the current execution context and can be redeclared. The concept of an execution context can be compared to a box. Per the ECMAScript Language Specification Section 8.3:
8.3 Execution Contexts
An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation. At any point in time, there is at most one execution context per agent that is actually executing code. This is known as the agent's running execution context.
[...]
Execution contexts for ECMAScript code have the additional state components listed in Table 22.
Table 22: Additional State Components for ECMAScript Code Execution Contexts
Component Purpose
LexicalEnvironment Identifies the Lexical Environment used to resolve identifier references made by code within this execution context.
VariableEnvironment Identifies the Lexical Environment whose EnvironmentRecord holds bindings created by VariableStatements within this execution context.
So everytime you write JavaScript code, it's separated into small individual "boxes" called execution contexts that are created whenever the interpreter encounters a new syntactic structure such as a block, function declaration, etc. In each of these boxes, there are many components, but in particular the LexicalEnvironment and VariableEnvironment. These are both "mini-boxes" inside the parent execution context "box" that hold references to the variables declared inside the current execution context that the code may access. LexicalEnvironment refers to the variables declared with let and const. VariableEnvironment refers to variables created with var.
Now looking at Section 13.3.2:
13.3.2 Variable Statement
NOTE A var statement declares variables that are scoped to the running execution context's VariableEnvironment. Var variables are created when their containing Lexical Environment is instantiated and are initialized to undefined when created. Within the scope of any VariableEnvironment a common BindingIdentifier may appear in more than one VariableDeclaration but those declarations collectively define only one variable.
The last part of the quoted note states the reason why you can declared a var more than once. Every time the interpreter encounters a function, a new VariableEnvironment is created because var is function-scoped, and if you're in the global scope there is one global VariableEnvironment. In your case, you've declared number both times in the global scope because { … } is a block, not a function, but they collectively only define number once. So, your code actually performs the same as this:
var number = 10 //declared once
{
number = 42 //since { … } does not create a new VariableEnvironment, number is the same
//variable as the one outside the block. Thus, the two declarations only define
//number once and every redeclaraction is essentially reassignment.
}
console.log(number) //42
And as you said, let and const are block-scoped. They do not throw an error because { … } creates a new block.
Why is var given a "free pass"? Does it have to do with the number property being reassigned to the window object when var is used?
As described before, a var declaraction can occur many times within a VariableEnvironment - but the same doesn't hole true for let and const. As Bergi mentioned, the ECMAScript authors hadn't realized the downfalls and quirks of having such a bad declarator var until much later, and changing var's behavior would cause the whole internet to collapse, as backwards-compatibility is a huge aspect of ECMAScript/JavaScript. Thus, the authors introduced new declarators, let and const that would aim to be block-scoped and predictable, more like other declarators you see in other languages. As such, let and const declarations throw an error whenever there is another declaration within the same scope. This has nothing to do with window, just because of compatibility and historical issues.

Related

ECMAScript: Where can we find specification about accessibility of let/const variables

Within the ECMAScript specification, where can we find a clear specification on why let and const are not accessible outside of Lexical Environments created with BlockStatements (as opposed to variables declared with var)?
If BlockStatements now create new lexical environments, then let and const declarations should not create variables accessible outside that lexical environment, but var variables should. I am trying to understand where exactly that behaviour is specified in the latest ECMAScript specification.
From 13.3.1 Let and Const Declarations:
let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated.
From 13.3.2 Variable Statement:
A var statement declares variables that are scoped to the running execution context's VariableEnvironment. Var variables are created when their containing Lexical Environment is instantiated and are initialized to undefined when created.
As seen, both variable declarations create variables when their containing Lexical Environment is instantiated. Which, in the case of a BlockStatement is when the compiler enters the block.
From 8.3 Execution Contexts:
LexicalEnvironment and VariableEnvironment components of an execution context are always Lexical Environments
As you saw in the description of var, it is scoped to the running execution context's VariableEnvironment. There is a top level VariableEnvironment and then a new one is created when you enter a function and then in this part about Execution Contexts, it says this:
The LexicalEnvironment and VariableEnvironment components of an execution context are always Lexical Environments. When an execution context is created its LexicalEnvironment and VariableEnvironment components initially have the same value.
So, at the start of a function, the LexicalEnvironment and VariableEnvironment are one and the same.
Then, down in 13.2.13 Runtime Semantics: Evaluation Block: { }, you can see that a new LexicalEnvironment is created when you enter the block and the prior one is restored when you leave the block. But, there is no mention of a new VariableEnvironment when you enter or leave a block (because that stays constant within the function).
So, since let and const are scoped to the LexicalEnvironment in which they are declared and that is local to a block, it would not be accessible outside the block.
But, var is scoped to the VariableEnvironment which is only created and scoped to the whole function, not to a block.
let and const variables can't be accessed outside their LexicalEnvironment because their definitions are not in the scope hierarchy as soon as the execution context leaves their block (as soon as you leave the block, their LexicalEnvironment is essentially popped off the stack and is no longer in the scope search chain for the interpreter to find variables).
When the specification adds this:
The [let and const] variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated.
This means that you are denied access to them even in their own Lexical Environment until their definition is evaluated. In layman's terms, this means they are not hoisted to the top of their scope like var is and therefore cannot be used until after their definition. This is implemented by not initializing a let or const variable in the LexicalEnvironment until its statement runs and the GetBindingValue() operation which looks up a variable will see that it is not yet initialized and will throw a ReferenceError. var variables are initialized immediately to undefined so they do not cause this ReferenceError.
You can see how this works in this code:
let x = 3;
function test() {
x = 1;
let x = 2;
console.log("hello");
}
test();
At the let x = 3 line a variable x is initialized in the outer LexicalEnvironment.
Then, when you call test(), at the start of that function, a new LexicalEnvironment is created, the new declaration for x in this block is placed into that new LexicalEnvironment, but not yet initialized.
Then, you get to the x = 1 statement. The interpreter looks up x, finds it in the current LexicalEnvironment, but it is uninitialized, so it throws a ReferenceError.
Per the question in your comment:
I've been turning the spec upside down, but have a hard time spotting that VariableEnvironments are only created for functions. Could you perhaps add an answer showing what steps in the spec you follow to reach said conclusion?
You have to just go through all the places in the spec where a VariableEnvironment is created and you will find that the only places this happens is the beginning of a function execution and at the top level.
For example, here's one on those places: PrepareForOrdinaryCall. There are a few others.
But, no place does it ever describe this happening for the start of a block, only the start of a function.
The way these specifications are written, they describe when things do happen, not when they don't happen (which makes some logical sense), but it means to prove something doesn't happen, you have to fail to find anywhere that says it does happen.

Is this how the temporal dead zone works?

I've been trying to figure out how the temporal dead zone/parsing of let and const work. This is what it seemingly boils down to (based on documentation and various responses I received in previous questions [such as this and this], though this goes against some answers given disagreement). Is this summary correct?
At the top of the scope, the JS engine creates a binding (an association of the variable keyword and name, e.g., let foo;) at the top of the relevant scope, which is considered hoisting the variable, but if you try to access the variable before the location of its declaration, JS throws a ReferenceError.
Once the JS engine moves down to the declaration (synonymous with "definition"), e.g., let foo;, the engine initializes it (allocating memory for it and making it accessible). The declaration is self-binding. (Here's the part that doesn't make sense to me: the binding is causing the hoisting at the top, but the engine doesn't initialize until it reaches the declaration, which also has a binding effect.) If there isn't an assignment, the value of the variable is set to undefined in the case of let or, if const is used, a SyntaxError will be thrown.
For reference here's what the specs say about it:
ECMAScript 2019 Language Specification draft: section 13.3.1, Let and Const Declarations
let and const declarations define variables that are scoped to the
running execution context's LexicalEnvironment. The variables are
created when their containing Lexical Environment is instantiated but
may not be accessed in any way until the variable's LexicalBinding is
evaluated. A variable defined by a LexicalBinding with an Initializer
is assigned the value of its Initializer's AssignmentExpression when
the LexicalBinding is evaluated, not when the variable is created. If
a LexicalBinding in a let declaration does not have an Initializer the
variable is assigned the value undefined when the LexicalBinding is
evaluated.
MDN Web Docs: Let
let bindings are created at the top of the (block) scope containing
the declaration, commonly referred to as "hoisting". Unlike variables
declared with var, which will start with the value undefined, let
variables are not initialized until their definition is evaluated.
Accessing the variable before the initialization results in a
ReferenceError. The variable is in a "temporal dead zone" from the
start of the block until the initialization is processed.
Maybe first you need to understand why the TDZ exists: because it prevents common surprising behaviour of variable hoisting and fixes a potential source of bugs. E.g.:
var foo = 'bar';
(function () {
console.log(foo);
var foo = 'baz';
})();
This is a frequent cause of surprise for many (novice) programmers. It's too late to change the behaviour of var now, so the ECMAScript group decided to at least fix the behaviour together with the introduction of let and const. How exactly it is implemented under the hood is a somewhat moot point, the important thing is that it stops what it most likely a typo/structural mistake dead in its tracks:
let foo = 'bar';
(function () {
console.log(foo);
let foo = 'baz';
})();
Practically speaking Javascript is executed in a two-step process:
Parsing of the code into an AST/executable byte code.
Runtime execution.
The parser will see var/let/const declarations in this first step and will set up scopes with reserved symbol names and such. That is hoisting. In the second step the code will act in that set up scope. It should be somewhat obvious that the parser/engine is free to do whatever it wants in that first step, and one of the things it does is to flag the TDZ internally, which will raise an error at runtime.
TDZ is quite complex to understand and requires a blog post to clarify how that actually works.
But in essence, overly simplified explanation is
let/const declarations do hoist, but they throw errors when accessed before being initialized (instead of returning undefined as var would)
Let's take this example
let x = 'outer scope';
(function() {
console.log(x);
let x = 'inner scope';
}());
the code above will throw a ReferenceError due to the TDZ semantics.
All these are from this great article completely about TDZ.
Kudos for the author.

Reassigning let variables [duplicate]

What is a brief introduction to lexical scoping?
I understand them through examples. :)
First, lexical scope (also called static scope), in C-like syntax:
void fun()
{
int x = 5;
void fun2()
{
printf("%d", x);
}
}
Every inner level can access its outer levels.
There is another way, called dynamic scope used by the first implementation of Lisp, again in a C-like syntax:
void fun()
{
printf("%d", x);
}
void dummy1()
{
int x = 5;
fun();
}
void dummy2()
{
int x = 10;
fun();
}
Here fun can either access x in dummy1 or dummy2, or any x in any function that call fun with x declared in it.
dummy1();
will print 5,
dummy2();
will print 10.
The first one is called static because it can be deduced at compile-time, and the second is called dynamic because the outer scope is dynamic and depends on the chain call of the functions.
I find static scoping easier for the eye. Most languages went this way eventually, even Lisp (can do both, right?). Dynamic scoping is like passing references of all variables to the called function.
As an example of why the compiler can not deduce the outer dynamic scope of a function, consider our last example. If we write something like this:
if(/* some condition */)
dummy1();
else
dummy2();
The call chain depends on a run time condition. If it is true, then the call chain looks like:
dummy1 --> fun()
If the condition is false:
dummy2 --> fun()
The outer scope of fun in both cases is the caller plus the caller of the caller and so on.
Just to mention that the C language does not allow nested functions nor dynamic scoping.
Lets try the shortest possible definition:
Lexical Scoping defines how variable names are resolved in nested functions: inner functions contain the scope of parent functions even if the parent function has returned.
That is all there is to it!
var scope = "I am global";
function whatismyscope(){
var scope = "I am just a local";
function func() {return scope;}
return func;
}
whatismyscope()()
The above code will return "I am just a local". It will not return "I am a global". Because the function func() counts where is was originally defined which is under the scope of function whatismyscope.
It will not bother from whatever it is being called(the global scope/from within another function even), that's why global scope value I am global will not be printed.
This is called lexical scoping where "functions are executed using the scope chain that was in effect when they were defined" - according to JavaScript Definition Guide.
Lexical scope is a very very powerful concept.
Lexical (AKA static) scoping refers to determining a variable's scope based solely on its position within the textual corpus of code. A variable always refers to its top-level environment. It's good to understand it in relation to dynamic scope.
Scope defines the area, where functions, variables and such are available. The availability of a variable for example is defined within its the context, let's say the function, file, or object, they are defined in. We usually call these local variables.
The lexical part means that you can derive the scope from reading the source code.
Lexical scope is also known as static scope.
Dynamic scope defines global variables that can be called or referenced from anywhere after being defined. Sometimes they are called global variables, even though global variables in most programmin languages are of lexical scope. This means, it can be derived from reading the code that the variable is available in this context. Maybe one has to follow a uses or includes clause to find the instatiation or definition, but the code/compiler knows about the variable in this place.
In dynamic scoping, by contrast, you search in the local function first, then you search in the function that called the local function, then you search in the function that called that function, and so on, up the call stack. "Dynamic" refers to change, in that the call stack can be different every time a given function is called, and so the function might hit different variables depending on where it is called from. (see here)
To see an interesting example for dynamic scope see here.
For further details see here and here.
Some examples in Delphi/Object Pascal
Delphi has lexical scope.
unit Main;
uses aUnit; // makes available all variables in interface section of aUnit
interface
var aGlobal: string; // global in the scope of all units that use Main;
type
TmyClass = class
strict private aPrivateVar: Integer; // only known by objects of this class type
// lexical: within class definition,
// reserved word private
public aPublicVar: double; // known to everyboday that has access to a
// object of this class type
end;
implementation
var aLocalGlobal: string; // known to all functions following
// the definition in this unit
end.
The closest Delphi gets to dynamic scope is the RegisterClass()/GetClass() function pair. For its use see here.
Let's say that the time RegisterClass([TmyClass]) is called to register a certain class cannot be predicted by reading the code (it gets called in a button click method called by the user), code calling GetClass('TmyClass') will get a result or not. The call to RegisterClass() does not have to be in the lexical scope of the unit using GetClass();
Another possibility for dynamic scope are anonymous methods (closures) in Delphi 2009, as they know the variables of their calling function. It does not follow the calling path from there recursively and therefore is not fully dynamic.
A lexical scope in JavaScript means that a variable defined outside a function can be accessible inside another function defined after the variable declaration. But the opposite is not true; the variables defined inside a function will not be accessible outside that function.
This concept is heavily used in closures in JavaScript.
Let's say we have the below code.
var x = 2;
var add = function() {
var y = 1;
return x + y;
};
Now, when you call add() --> this will print 3.
So, the add() function is accessing the global variable x which is defined before method function add. This is called due to lexical scoping in JavaScript.
Lexical scope means that in a nested group of functions, the inner functions have access to the variables and other resources of their parent scope.
This means that the child's functions are lexically bound to the execution context of their parents.
Lexical scope is sometimes also referred to as static scope.
function grandfather() {
var name = 'Hammad';
// 'likes' is not accessible here
function parent() {
// 'name' is accessible here
// 'likes' is not accessible here
function child() {
// Innermost level of the scope chain
// 'name' is also accessible here
var likes = 'Coding';
}
}
}
The thing you will notice about lexical scope is that it works forward, meaning the name can be accessed by its children's execution contexts.
But it doesn't work backward to its parents, meaning that the variable likes cannot be accessed by its parents.
This also tells us that variables having the same name in different execution contexts gain precedence from top to bottom of the execution stack.
A variable, having a name similar to another variable, in the innermost function (topmost context of the execution stack) will have higher precedence.
Source.
I love the fully featured, language-agnostic answers from folks like #Arak. Since this question was tagged JavaScript though, I'd like to chip in some notes very specific to this language.
In JavaScript our choices for scoping are:
as-is (no scope adjustment)
lexical var _this = this; function callback(){ console.log(_this); }
bound callback.bind(this)
It's worth noting, I think, that JavaScript doesn't really have dynamic scoping. .bind adjusts the this keyword, and that's close, but not technically the same.
Here is an example demonstrating both approaches. You do this every time you make a decision about how to scope callbacks so this applies to promises, event handlers, and more.
Lexical
Here is what you might term Lexical Scoping of callbacks in JavaScript:
var downloadManager = {
initialize: function() {
var _this = this; // Set up `_this` for lexical access
$('.downloadLink').on('click', function () {
_this.startDownload();
});
},
startDownload: function(){
this.thinking = true;
// Request the file from the server and bind more callbacks for when it returns success or failure
}
//...
};
Bound
Another way to scope is to use Function.prototype.bind:
var downloadManager = {
initialize: function() {
$('.downloadLink').on('click', function () {
this.startDownload();
}.bind(this)); // Create a function object bound to `this`
}
//...
These methods are, as far as I know, behaviorally equivalent.
In simple language, lexical scope is a variable defined outside your scope or upper scope is automatically available inside your scope which means you don't need to pass it there.
Example:
let str="JavaScript";
const myFun = () => {
console.log(str);
}
myFun();
// Output: JavaScript
Lexical scope means that a function looks up variables in the context where it was defined, and not in the scope immediately around it.
Look at how lexical scope works in Lisp if you want more detail. The selected answer by Kyle Cronin in Dynamic and Lexical variables in Common Lisp is a lot clearer than the answers here.
Coincidentally I only learned about this in a Lisp class, and it happens to apply in JavaScript as well.
I ran this code in Chrome's console.
// JavaScript Equivalent Lisp
var x = 5; //(setf x 5)
console.debug(x); //(print x)
function print_x(){ //(defun print-x ()
console.debug(x); // (print x)
} //)
(function(){ //(let
var x = 10; // ((x 10))
console.debug(x); // (print x)
print_x(); // (print-x)
})(); //)
Output:
5
10
5
Lexical scoping: Variables declared outside of a function are global variables and are visible everywhere in a JavaScript program. Variables declared inside a function have function scope and are visible only to code that appears inside that function.
IBM defines it as:
The portion of a program or segment unit in which a declaration
applies. An identifier declared in a routine is known within that
routine and within all nested routines. If a nested routine declares
an item with the same name, the outer item is not available in the
nested routine.
Example 1:
function x() {
/*
Variable 'a' is only available to function 'x' and function 'y'.
In other words the area defined by 'x' is the lexical scope of
variable 'a'
*/
var a = "I am a";
function y() {
console.log( a )
}
y();
}
// outputs 'I am a'
x();
Example 2:
function x() {
var a = "I am a";
function y() {
/*
If a nested routine declares an item with the same name,
the outer item is not available in the nested routine.
*/
var a = 'I am inner a';
console.log( a )
}
y();
}
// outputs 'I am inner a'
x();
I hope this is helpful, here is my attempt at a slightly more abstract definition:
Lexical scope:
The access or range something (e.g. a function or variable) has to other elements in the program as determined by its position in the source code.
Fwiw, my logic here simply builds from the definitions of:
Lexical: relating to the words or vocabulary of a language (specifically the word as separate from it's grammar or construction) {in our case - a programming language}.
Scope (noun): the range of operation {in our case the range is: what can be accessed}.
Note, the original 1960 definition of Lexical Scope from the ALGOL 60 spec is far more pithy than my attempt above:
Lexical scope: the portion of source code in which a binding of a name with an entity applies. source
Lexical scope refers to the lexicon of identifiers (e.g., variables, functions, etc.) visible from the current position in the execution stack.
- global execution context
- foo
- bar
- function1 execution context
- foo2
- bar2
- function2 execution context
- foo3
- bar3
foo and bar are always within the lexicon of available identifiers because they are global.
When function1 is executed, it has access to a lexicon of foo2, bar2, foo, and bar.
When function2 is executed, it has access to a lexicon of foo3, bar3, foo2, bar2, foo, and bar.
The reason global and/or outer functions do not have access to an inner functions identifiers is because the execution of that function has not occurred yet and therefore, none of its identifiers have been allocated to memory. What’s more, once that inner context executes, it is removed from the execution stack, meaning that all of it’s identifiers have been garbage collected and are no longer available.
Finally, this is why a nested execution context can ALWAYS access it’s ancestors execution context and thus why it has access to a greater lexicon of identifiers.
See:
https://tylermcginnis.com/ultimate-guide-to-execution-contexts-hoisting-scopes-and-closures-in-javascript/
https://developer.mozilla.org/en-US/docs/Glossary/Identifier
Special thanks to #robr3rd for help simplifying the above definition.
There is an important part of the conversation surrounding lexical and dynamic scoping that is missing: a plain explanation of the lifetime of the scoped variable - or when the variable can be accessed.
Dynamic scoping only very loosely corresponds to "global" scoping in the way that we traditionally think about it (the reason I bring up the comparison between the two is that it has already been mentioned - and I don't particularly like the linked article's explanation); it is probably best we don't make the comparison between global and dynamic - though supposedly, according to the linked article, "...[it] is useful as a substitute for globally scoped variables."
So, in plain English, what's the important distinction between the two scoping mechanisms?
Lexical scoping has been defined very well throughout the answers above: lexically scoped variables are available - or, accessible - at the local level of the function in which it was defined.
However - as it is not the focus of the OP - dynamic scoping has not received a great deal of attention and the attention it has received means it probably needs a bit more (that's not a criticism of other answers, but rather a "oh, that answer made we wish there was a bit more"). So, here's a little bit more:
Dynamic scoping means that a variable is accessible to the larger program during the lifetime of the function call - or, while the function is executing. Really, Wikipedia actually does a nice job with the explanation of the difference between the two. So as not to obfuscate it, here is the text that describes dynamic scoping:
...[I]n dynamic scoping (or dynamic scope), if a variable name's scope is a
certain function, then its scope is the time-period during which the
function is executing: while the function is running, the variable
name exists, and is bound to its variable, but after the function
returns, the variable name does not exist.
Ancient question, but here is my take on it.
Lexical (static) scope refers to the scope of a variable in the source code.
In a language like JavaScript, where functions can be passed around and attached and re-attached to miscellaneous objects, you might have though that scope would depend on who’s calling the function at the time, but it doesn’t. Changing the scope that way would be dynamic scope, and JavaScript doesn’t do that, except possibly with the this object reference.
To illustrate the point:
var a='apple';
function doit() {
var a='aardvark';
return function() {
alert(a);
}
}
var test=doit();
test();
In the example, the variable a is defined globally, but shadowed in the doit() function. This function returns another function which, as you see, relies on the a variable outside of its own scope.
If you run this, you will find that the value used is aardvark, not apple which, though it is in the scope of the test() function, is not in the lexical scope of the original function. That is, the scope used is the scope as it appears in the source code, not the scope where the function is actually used.
This fact can have annoying consequences. For example, you might decide that it’s easier to organise your functions separately, and then use them when the time comes, such as in an event handler:
var a='apple',b='banana';
function init() {
var a='aardvark',b='bandicoot';
document.querySelector('button#a').onclick=function(event) {
alert(a);
}
document.querySelector('button#b').onclick=doB;
}
function doB(event) {
alert(b);
}
init();
<button id="a">A</button>
<button id="b">B</button>
This code sample does one of each. You can see that because of lexical scoping, button A uses the inner variable, while button B doesn’t. You may end up nesting functions more than you would have liked.
By the way, in both examples, you will also notice that the inner lexically scoped variables persist even though the containing function function has run its course. This is called closure, and refers to a nested function’s access to outer variables, even if the outer function has finished. JavaScript needs to be smart enough to determine whether those variables are no longer needed, and if not, can garbage collect them.
Here's a different angle on this question that we can get by taking a step back and looking at the role of scoping in the larger framework of interpretation (running a program). In other words, imagine that you were building an interpreter (or compiler) for a language and were responsible for computing the output, given a program and some input to it.
Interpretation involves keeping track of three things:
State - namely, variables and referenced memory locations on the heap and stack.
Operations on that state - namely, every line of code in your program
The environment in which a given operation runs - namely, the projection of state on an operation.
An interpreter starts at the first line of code in a program, computes its environment, runs the line in that environment and captures its effect on the program's state. It then follows the program's control flow to execute the next line of code, and repeats the process till the program ends.
The way you compute the environment for any operation is through a formal set of rules defined by the programming language. The term "binding" is frequently used to describe the mapping of the overall state of the program to a value in the environment. Note that by "overall state" we do not mean global state, but rather the sum total of every reachable definition, at any point in the execution).
This is the framework in which the scoping problem is defined. Now to the next part of what our options are.
As the implementor of the interpreter, you could simplify your task by making the environment as close as possible to the program's state. Accordingly, the environment of a line of code would simply be defined by environment of the previous line of code with the effects of that operation applied to it, regardless of whether the previous line was an assignment, a function call, return from a function, or a control structure such as a while loop.
This is the gist of dynamic scoping, wherein the environment that any code runs in is bound to the state of the program as defined by its execution context.
Or, you could think of a programmer using your language and simplify his or her task of keeping track of the values a variable can take. There are way too many paths and too much complexity involved in reasoning about the outcome the totality of past execution. Lexical Scoping helps do this by restricting the current environment to the portion of state defined in the current block, function or other unit of scope, and its parent (i.e. the block enclosing the current clock, or the function that called the present function).
In other words, with lexical scope the environment that any code sees is bound to state associated with a scope defined explicitly in the language, such as a block or a function.
Scope is the context within which a variable/binding is accessible. Lexical scope means local to the enclosing lexical block or blocks as opposed to for instance global scope.
This topic is strongly related with the built-in bind function and introduced in ECMAScript 6 Arrow Functions. It was really annoying, because for every new "class" (function actually) method we wanted to use, we had to bind this in order to have access to the scope.
JavaScript by default doesn't set its scope of this on functions (it doesn't set the context on this). By default you have to explicitly say which context you want to have.
The arrow functions automatically gets so-called lexical scope (have access to variable's definition in its containing block). When using arrow functions it automatically binds this to the place where the arrow function was defined in the first place, and the context of this arrow functions is its containing block.
See how it works in practice on the simplest examples below.
Before Arrow Functions (no lexical scope by default):
const programming = {
language: "JavaScript",
getLanguage: function() {
return this.language;
}
}
const globalScope = programming.getLanguage;
console.log(globalScope()); // Output: undefined
const localScope = programming.getLanguage.bind(programming);
console.log(localScope()); // Output: "JavaScript"
With arrow functions (lexical scope by default):
const programming = {
language: "JavaScript",
getLanguage: function() {
return this.language;
}
}
const arrowFunction = () => {
console.log(programming.getLanguage());
}
arrowFunction(); // Output: "JavaScript"
Lexical scoping means functions resolve free variables from the scope where they were defined, not from the scope where they are called.
I normally learn by example, and here's a little something:
const lives = 0;
function catCircus () {
this.lives = 1;
const lives = 2;
const cat1 = {
lives: 5,
jumps: () => {
console.log(this.lives);
}
};
cat1.jumps(); // 1
console.log(cat1); // { lives: 5, jumps: [Function: jumps] }
const cat2 = {
lives: 5,
jumps: () => {
console.log(lives);
}
};
cat2.jumps(); // 2
console.log(cat2); // { lives: 5, jumps: [Function: jumps] }
const cat3 = {
lives: 5,
jumps: () => {
const lives = 3;
console.log(lives);
}
};
cat3.jumps(); // 3
console.log(cat3); // { lives: 5, jumps: [Function: jumps] }
const cat4 = {
lives: 5,
jumps: function () {
console.log(lives);
}
};
cat4.jumps(); // 2
console.log(cat4); // { lives: 5, jumps: [Function: jumps] }
const cat5 = {
lives: 5,
jumps: function () {
var lives = 4;
console.log(lives);
}
};
cat5.jumps(); // 4
console.log(cat5); // { lives: 5, jumps: [Function: jumps] }
const cat6 = {
lives: 5,
jumps: function () {
console.log(this.lives);
}
};
cat6.jumps(); // 5
console.log(cat6); // { lives: 5, jumps: [Function: jumps] }
const cat7 = {
lives: 5,
jumps: function thrownOutOfWindow () {
console.log(this.lives);
}
};
cat7.jumps(); // 5
console.log(cat7); // { lives: 5, jumps: [Function: thrownOutOfWindow] }
}
catCircus();

Javascript scoping change

I read a lot about javascript scoping but still can't seem to understand why this code (this line the left arrow points to) changing geval scope to be the global scope and not the function scope. geval is invoked inside test2 so I thought it would have the same scope..
const test2 = () => {
var x = 2, y = 4;
console.log(eval('x + y')); // Direct call, uses local scope, result is 6
var geval = eval; // equivalent to calling eval in the global scope
console.log(geval('x + y')); // Indirect call, uses global scope, throws ReferenceError because `x` is undefined
(0, eval)('x + y'); // another example of Indirect call
}
The following excerpts are taken from ECMA-262 7th edition (ECMAScript 2016) – section numbers sometimes differ between versions.
18.2.1.1 Runtime Semantics: PerformEval( x, evalRealm, strictCaller, direct)
...
9. If direct is true, then
a. Let lexEnv be NewDeclarativeEnvironment(ctx's LexicalEnvironment).
b. Let varEnv be ctx's VariableEnvironment.
10. Else,
a. Let lexEnv be NewDeclarativeEnvironment(evalRealm.[[GlobalEnv]]).
b. Let varEnv be evalRealm.[[GlobalEnv]].
So for an indirect call (not calling eval by name) you arrive at step 10 which calls NewDeclarativeEnvironment(E) with the global environment record as parameter value.
NewDeclarativeEnvironment is described in section 8.1.2.2. As expected this creates an environment record for any variables defined using let within script parsed by eval, and sets the "outer lexical environment reference" of that record to the environment record provided as argument.
Step 10.b sets the variable environment record, for named functions and variables declared with var, to the global environment record of the realm eval was called in – meaning window in a browser document.
In short, calling eval indirectly creates a separate environment record for let variables declared within the code being evaluated, whose next outer scope (lexical reference) is the global object, but uses the global object for var and named function declarations.
If you want to have evaluated code inherit the scope of surrounding code, make a direct call using the name eval as the function reference.
The presence of both 9.a and 10.a means that variables declared with let are not retained after a call to eval no matter what the type of call..
The Historical Why (edit)
The behavior of indirect calls is likely the result of deprecating calling eval on an object and removing the eval property from Object.prototype.
From the JavaScript 1.2 reference for Object data type methods:
eval Evaluates a string of JavaScript code in the context of the specified object.
and calling eval on an object in such early versions of JavaScript would result in code evaluation as if it were inside a with (object) {} statement.
The rules for indirect calls replace this behavior with a standard response: if an object method is set to eval the previous behavior of object.eval is not recreated and the scope of evaluated code is the global object exceot for let variables. This is similar to the way creating a function from text with new Function behaves as if it were defined in global scope. It also has the effect that the this value seen by indirect calls to eval is the global object (this values reside in environmental records).

let keyword - Dictionaries of Dictionaries data model

This query is related to storage structure but not to understand the scope rule for let keyword.
Using var keyword, a is a property of window object(like a dictionary)
var a = 10;
console.log(window.a); // 10
console.log(window['a']) // 10
fis a property of window object
function f(){}
console.log(window['f']) // function object
Using let keyword, b is not a property of window dictionary
let b = 20;
console.log(window.b); // undefined
My understanding is, any name(function/var/..) introduced in a JavaScript code will be a property(member) of window dictionary(nested) object.
Edit:
Whose property is b?
None of the code you've supplied relates to a "dictionary".
var vs. let determines the scope that the variable has. With var, the variable is scoped to its containing function (or the Global scope, if outside of all functions).
let gives the variable block level scope which can be more granular than function level scope (i.e. branches of an if/else statement).
If the declaration is in the Global scope, then let vs. var won't make any difference because the scope is Global.
Both declarations will create global variables, but let doesn't explicitly create a named property on the window object as will happen with var.
See this for details.
Also from MDN:
let allows you to declare variables that are limited in scope to the
block, statement, or expression on which it is used. This is unlike
the var keyword, which defines a variable globally, or locally to an
entire function regardless of block scope.
And, from the ECMAScript spec. itself:
let and const declarations define variables that are scoped to the
running execution context’s LexicalEnvironment.
A var statement declares variables that are scoped to the running
execution context’s VariableEnvironment.
This is why globally declared let variables are not accessible via window like globally declared var variables are.

Categories

Resources