When I run the following code written using ES6 let:
function doSmth(){
age=27;
}
let age;
doSmth();
console.log(age);
I get the output correctly as 27.
Now I am a bit confused as to how this works. Specifically, I have read is that let has a block scope.
So, how is it that let age, which is declared outside the function doSmth, is the same variable inside the function doSmth?
Is that something to do with global scope, but if yes, then what role does block scope have exactly ?
"Let has block scope" doesn't mean that the variable must be defined in the same block - it means that the level at which the variable is defined can be a block.
In contrast, the level at which a var variable is defined cannot be a block, unless that block is also a function block. If a var variable is initialized inside a non-function block, it will be hoisted out to the nearest outer block which is also a function block.
No matter whether a variable is declared with var, let, or const, it will be referencable anywhere else inside the same block in which it was declared, including inside child blocks. (For let and const, the line that declares the variable, eg const foo =, must also have run for a reference to foo to work without throwing.)
Another way of looking at it is: if a variable is referenced somewhere, the interpreter looks for whether it's initialized in the current block. If so, that's the binding that's used. Otherwise, it looks in the next outer block to see if the variable is initialized there - if so, that's the binding that's used. And so on. If it gets all the way to the top level, and no binding has been found, and it's not a property of the global object, that reference will throw a ReferenceError.
In your code, the age variable happens to be global, since it's defined on the top level, but that doesn't really matter - the code would work the same if age was just an an outer block, but not on the top level, eg:
function foo() {
function doSmth() {
age = 27;
}
let age;
doSmth();
console.log(age);
}
foo();
Now I am a bit confused as to how this works. Specifically, I have read is that let has a block scope.
Yes, let has block scope. But a block such as inside your doSmmth() function has access to ALL the variables in parent scopes that have been defined or hoisted at the time of the function execution.
So, let age; is in the parent scope and has been defined by the time doSmth() executes. Therefore the interpreter tasked with looking up a variable named age finds it in the parent scope just fine.
So, how is it that let age, which is declared outside the function doSmth, is the same variable inside the function doSmth?
Here's how the interpreter works in this regard. Your code executes in these steps:
Parse the code you've presented.
The function doSmth() definition is encountered. Functions are hoisted to the top of the containing function scope so that definition is immediately added to this scope object. let age is also encountered in the parsing and is added to the parsed scope, but it is marked in a way that it will not yet be accessible to code.
Start executing the code. A new scope object is created from the parsed code.
let age; is encountered. This marks the age variable which was previous put into this scope at parsing time as now available for code to use.
Call doSmth(). This pushes a return address on the callstack and as it sets up to execute doSmth(), it creates a new function scope.
It then executes age = 27 inside that function. The interpreter looks in the local scope for a symbol named age. It doesn't find one. So, it goes up the scope chain and looks in the parent scope. It finds a matching symbol and uses that one.
The function returns which pops a return address off the call stack and resets the current scope back to the parent scope and the execution goes to right after where doSmth() was and executes the console.log(age).
The interpreter looks in the local scope for a symbol named age and finds it and uses it.
So, the main keys here that it sounds like you may be confused by are:
If a symbol isn't found in the local scope, then the parent scopes are searched for the symbol name.
let does create a block-scoped symbol, but it can still be accessed by any child functions also declared in that scope.
In this particular code, there wouldn't be any difference between let and var and it isn't hoisting that makes this work. It's parent scopes.
In your code, both doSmth and age are in global scope. Windows for browser and module object for node. So they basically are in the same scope.
Your code will go through two phases.
Compilation where all hoisting will occur (var declared variables).
Execution where actual execution of code will occur.
In first phase you will declare function doSmith and age variable. Before executeion phase JS engine knows what is age variable and what is doSmith.
In next phase i.e. execution phase, execution will occur,
doSmth();
console.log(age);
will get executed. Hence in age variable is accessible to function doSmith in execution phase.
Related
var name = 'ali'
function sayHi() {
console.log(name);
console.log(age);
var name = 'Lydia';
let age = 21;
}
sayHi()
When i put this code AST explorer and run it. I actually see that name has already been assigned to variable 'Lydia' before the code is executed.I know AST is created before the code is run . But when the code is executed it returns undefined. I am very confused about this please can you help me
There are two variables with the same name (name) in that snippet. If you look closely at the generated AST, you will see both declarations.
The AST does not show the scopes of the two declarations, which is an important aspect. The scope of a variable is the region of the program in which the variable's name is visible; that is, associated with the variable. It is perfectly legal for two variables to have the same name; if they have overlapping scopes, the inner scope will override the outer scope.
All of that is common to most programming languages, but ECMAScript's scoping rules are rather more complicated than average (which, in part, is why it would be useful for the AST tool to show scopes). ECMAScript features two different sets of scoping rules, one for variables declared with var, and another for variables declared with let or const.
While this simple example doesn't show all the differences, it certainly does show one important difference: while the inner var-declared name can be used before it has been initialised, resulting in the value undefined, the let-declared age cannot be used before initialisation, resulting in an error being thrown. (let-declared variables are in what's called a "Temporal Dead Zone" until they are initialised.)
The difference which is not shown in this example is the boundaries of the scopes of the variables declared inside the function sayHi. The scope of a var-declared variable is the innermost function body which it is declared in (including any functions declared in the same scope, unless they have an overriding declaration of the same name). The scope of a let-declared variable, on the other hand, is limited to the innermost enclosing block. But in this example, the innermost enclosing block of both the inner var name and let age is the body of the function sayHi.
In ECMAScript, the scope of a variable is always an entire block. This contrasts with languages such as C in which the scope of a variable only extends from the variable's declaration until the end of the enclosing block. This would make a difference in this case: Under C scoping rules, name in console.log(name) would refer to the outer declaration, because the inner declaration is further down in the code and so its scope hasn't started yet. And age in console.log(age) would be illegal because there is no enclosing scope in which age is declared. But in ECMAScript, declarations are "hoisted" to encompass the entire block, creating the possibility of Temporal Dead Zones.
While this is probably already Too Much Information, I should note that I haven't really covered all of the odd wrinkles in scoping in either ECMAScript or C, and that other languages have other subtly different scoping rules.
As we know, traditionally JS has been lacking block scope. As we know JS has had only function scope up until recently.
In recent versions of JS though, we can have let and const variables which are visible only in the scope where they are defined.
But... deep down... how is this done/implemented really? Is there really now in the language a first-class notion of block scope in JS, or is the block scope thing just some simulation to make certain variables visible only in the block where they are defined?
I mean is block scope in recent JS versions a first-class thing, just like function scope is, or is block scope just some sort of simulation while we actually still have just the old good function scope?
But... deep down... how is this done/implemented really? Is there really now in the language a first-class notion of block scope in JS...?
Yes, there is. A new block¹ creates a new lexical environment in the same way that creating a function does (without, obviously, all the other aspects of creating a function). You can see that in the Evaluation section of blocks in the spec.
It's a first-class language construct.
¹ I originally wrote "...containing a let, const, or class declaration..." but the specification doesn't actually make that distinction, though I expect JavaScript engines do (since there's no need for a new environment if there are no lexically-declared bindings).
In a comment you've asked:
What about hoisting? I read that block scoped-variables are hoisted to the top of the block they are defined in... but then also... You get an error if you try to access a block-scoped variable before the line/statement where it is declared in its block? This sounds contradictory, no? If they are hoisted we would not be getting this error but we would be getting undefined. What is the truth here?
In my book I describe them as half-hoisted: The creation of the variable (more generally, the "binding") is hoisted to the top of the scope in which its declaration (let x or whatever) appears (the block, in this case), but the binding isn't initialized until the declaration is reached in the step-by-step execution of the code. The time between creation and initialization is called the Temporal Dead Zone. You can't use the binding (at all) within the TDZ.
This only applies to let, const, and class declarations. var is handled differently in two ways: 1. Obviously, var is hoisted to the top of the function (or global) scope, not just block scope. 2. Less obviously, var bindings are both created and initialized (with the value undefined) upon entry to the scope. They're fully hoisted. (The declaration of them is; any initializer on the var statement is actually an assignment, and done when that statement is reached in the step-by-step execution of the code.)
Here's an example:
function foo(n) {
// `l1` and `l2` don't exist here at all here
// `v` is created and initialized with `undefined` here, so we can happily
// do this:
console.log(`v = ${v}`);
if (n > 10) {
// `l1` and `l2` are created here but not initialized; if we tried to
// use them here, we'd get an error; uncomment this line to see it:
// console.log(`l1 = ${l1}`);
console.log("n is > 10");
var v = "a"; // `v` is assigned the value `"a"` here, replacing the
// value `undefined`
let l1 = "b"; // `l1` is initialized with the value `"b"` here
console.log(`l1 = ${l1}`);
let l2; // `l2` is initialized with the value `undefined `here
console.log(`l2 = ${l2}`);
l2 = "c"; // `l2` is assigned the value `"c"` here, replacing the
// value `undefined`
console.log(`l2 = ${l2}`);
}
}
foo(20);
Just for completeness, function declarations are also fully-hoisted, but even more so than var: The function is actually created and assigned to the binding upon entry to the scope (unlike var, which gets the value undefined).
In a comment you've observed:
Then... I don't see what's the difference between no hoisting and half-hoisting...
Good point, I didn't explain that. The difference relates to shadowing identifiers in the outer scope. Consider:
function foo() {
let a = 1;
if (/*...*/) {
console.log(`a = ${a}`);
let a = 2;
// ...
}
}
What should the log show?
Sorry, that was a trick question; the log doesn't show anything, because you get an error trying to use a there, because the inner a declaration shadows (hides) the outer a declaration, but the inner a isn't initialized yet, so you can't use it yet. It's in the TDZ.
It would have been possible to make the outer a accessible there, or to make the inner a accessible there with the value undefined (e.g., fully hoisting it like var, but just within the block), but both of those have problems the TDZ helps solve. (Specifically: Using the outer a would have been confusing for programmers [a means one thing at the beginning of the block but something else later?!] and would have meant JavaScript engines had to create new lexical environments all over the place, basically every let or const or class would introduce a new one. And pre-initializing with undefined is confusing for programmers, as var has shown us over the years...)
In this code, how many variables are in the global scope, and how many are in the local scope of the greet function?
var foo = 42;
var bar = 43;
function greet(name) {
greeting = 'Hello';
return greeting + ' ' + name;
}
I believe there are two global variables (foo and bar) and 0 local variables. Since the function never gets called, name and greeting don't get declared so I would say that they are not in the global or local scope.
What do you think? Can greeting and name have a scope if the function greet() is never invoked?
This is somewhat of a theoretical question along the lines of "if a tree falls in the forest...".
The answer depends on whether you are analyzing the problem from a static lexical perspective vs the dynamic execution environment. For the latter, your analysis is accurate, although. If the code above is executed, there will be three identifiers defined in the global space, the two vars and the function. The greeting var and name argument will not "come into existence".
That said, if I were in a code review evaluating this statically, I would have to ask "well isn't there a purpose to defining the function greet"? The assumption must be that it is eventually intended to be called - otherwise why write it (or for that matter, any of the code above). And as such, the use of that function would definitely introduce a global variable.
Source - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
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.
The variables foo and bar are declared at the top. So they will be assigned directly to the window object (global scope).
Assigning a value to an undeclared variable implicitly creates it as a
global variable (it becomes a property of the global object) when the
assignment is executed.
Undeclared variables do not exist until the code assigning to them is
executed.
The variable greeting is not declared and is present within the function greet which is not called. So, this will not create the variable greeting until the function is called and if called it will create greeting as a global variable.
name is not a variable, it's a parameter and acts like a placeholder.
In short, there are two global variables - foo and bar and if function greet is invoked, then there will be three global variables foo, bar and greeting.
Can greeting and name have a scope if the function greet() is never invoked?
Yes, name exists already in the local scope as although the function wasn't executed yet, it was still parsed.
greeting however will only exist when the function gets called, but wether its in the global scope is not relevant as its just bad practice to have implicit globals.
I am going through the book JavaScript: The Complete Reference, Third Edition By: Thomas Powell; Fritz Schneider to have a detailed understanding of the concepts.
Scoping Rules
Outside of a function or object, variables are within the global space whether explicitly defined with var or not. Within a function or object, if the var statement is used, the defined variable will be local to the construct; without the statement, it will be global.
Commonly, JavaScript developers make assumptions about scoping rules with var that aren’t quite true. For example, a var statement found within a for loop does not scope that value to the loop. In this case, it is scoped to either the function it is within or to the global space if it is outside a function or an object.
Just to see what happens consequently, I coded like this,
When I press Ctrl+Space in Eclipse IDE for it to show JavaScript proposals, why am I able to access the variable jLocal outside the function?
As per the author description:
For example, a var statement found within a for loop does not scope that value to the loop. In this case, it is scoped to either the function it is within or to the global space if it is outside a function or an object.
Because at the bottom of your code you have:
...
jLocal = jLocal + j; // defined not in any functions
...
Making it global, but not necessary defined.
It isn't the case of a local function. myFunc is global, just as the variable jLocal is (albeit the name). Because of hoisting, jLocal is assumed to be declared on top of parent scope.
Looking more carefully, there's two variable's named jLocal. One local to myFunc and an implicit one on global scope.
Want a tip?
Put "use strict"; just before var global1 = true;. An HTML 5 implementation would be able to catch and show your error.
Just now,I saw some code like this:
if(condition){
var xx='sss';
}
//do something
if(condition){
console.info(xx);
}
Now, I just wonder why the second if statement work? How can it access the xx variable since it is a local variable defined in another if statement?
var in JavaScript is scoped to the containing execution context (e.g., the whole function's scope, or the whole global scope if the var is at global scope), not the block. JavaScript doesn't (yet) have block scope (ECMAScript6 looks likely to add it, via the new let keyword).
The code you've quoted is exactly equivalent to this:
var xx;
if(condition){
xx='sss';
}
//do something
if(condition){
console.info(xx);
}
This is covered by Section 10.5 of the specification, which describes what the engine does when entering a new execution context. (It's basically a two-phase process, first setting up all the declarations, and then executing step-by-step code.)
More: Poor misunderstood var
In JavaScript the scope is exacted to the closure (the last enclosing function block, or defaults to the window object). When a variable is declared anywhere within that function block it is hoisted to the top of the scope, so in essence a variable exists as undefined starting at the very top of the scope if it is declared anywhere in the scope.
Think of it like this, when the code begins executing it scans all the instructions for declarations and allocates the symbol name starting immediately.
console.log(x); // undefined
console.log(y); // error: Uncaught ReferenceError: y is not defined
var x;
for that matter you can take it to extremes:
console.log(x); // undefined, not an error
while (false) {
if (false) {
var x;
}
}
even though var x can't possibly be reached, and during execution would be optimized away completely. the engine will still hoist the variable to the top of the scope
hope this helps -ck
useful link: http://www.youtube.com/watch?v=taaEzHI9xyY&feature=youtu.be#t=42m57s
var declarations affect the entire scope of the smallest containing function or program. JavaScript is not block scoped.
Crock says:
Variable Declarations
All variables should be declared before used. JavaScript does not require this, but doing so makes the program easier to read and makes it easier to detect undeclared variables that may become implied globals. Implied global variables should never be used.
The var statements should be the first statements in the function body.
It is preferred that each variable be given its own line and comment. They should be listed in alphabetical order.
var currentEntry; // currently selected table entry
var level; // indentation level
var size; // size of table
JavaScript does not have block scope, so defining variables in blocks can confuse programmers who are experienced with other C family languages. Define all variables at the top of the function.
Use of global variables should be minimized. Implied global variables should never be used.
Note, this is changing with the let statement, and in current JavaScript (EcmaScript 5), the variable name in a catch block is block scoped.
javascript doesn't have block scope, so var xx='sss' is either locally scoped (if your sample code is inside a function) or globally scoped (if your sample code is not contained in a function).
JavaScript is a dynamic language, that isn't always picky about things like variable scoping. So, this "feature" allows you to write the code
if (condition) {
var xx = 'sss';
} else {
var xx = 'ttt';
}
// do something
if (condition) {
console.info(xx);
}
I would recommend avoiding this, since it makes your program harder to understanda and reason about.
If you declare a variable using var within a function, the variable is scoped to within that function. An if statement is not a function.
I'm assuming in this case both if statements are within the same function and therefore xx is in scope?
If a variable is declared inside a conditional statement, it is still available anywhere following the declaration in the containing function (or globally if the conditional is not in a function). However, it will equal undefined if the condition evaluates to false, unless the variable is later assigned a value.
If a local variable is referenced globally or in another function, a JavaScript error will occur. Depending on your browser, the error may say the variable "is not defined" or "is undefined". This is different from the variable equaling undefined like mentioned above, because a variable that equals undefined can still be referenced.