Why does the code below doesn't throw a warning/error about variable re-assignment?
I spent a good amount of time debugging something like this because of the trailing comma.
When the code inside the else statement is moved outside of the if scope, it throws the proper error.
Any explanation would be great.
let a
if (false) {
// Nothing
} else {
const b = 100,
a = 10
}
console.log(a)
// undefined
Let and const variables are not hoisted and are created in block scope.
So in your code, you are creating another variable a which is a const. and once scope is over, it will again point to a that is outside.
For example, if you try to reassign it in the same block, it will throw error as you cannot change value of const.
let a
if (false) {
// Nothing
} else {
const b = 100,
a = 10
a = 2;
}
console.log(a)
// undefined
As pointed out by Bergi, all variables are hoisted. But they are hoisted in a way to give impression that they are not hoisted. You can read full description here: Are variables declared with let or const not hoisted in ES6?
Why does the code not throw a warning/error about variable re-assignment?
Because you are not re-assigning a variable (which is what you actually wanted, assign the let a variable), you are re-declaring it with that const - and that in a different scope (in this case, the else block). If you had re-declared it in the same scope (e.g. let a; const a = 5) it would have thrown an error indeed. A shadowing declaration (that hides an existing names) in an inner scope is not an error, it is in fact necessary for local scopes to work and not be affected by their surroundings.
If you want to get a warning about this, use a linter that has a rule against shadowing declarations, e.g. in ESLint.
In else condition, a is re declared as const.So whatever the value (u assigned 10) within in else condition. you have to put ; at end of b declaration. otherwise a will be declared as const.So, a value is undefined in the outside of else condition.
let a
if (false) {
// Nothing
} else {
const b = 100;
a = 10
}
console.log(a)
in that case, it is because it never gets into the else statement, it is always false.
I usually use alert(string); to debug and know the value of variables, try one at the beginning of the if, or use console.log()
I also recommend you to read the following regarding let or var:
What's the difference between using "let" and "var" to declare a variable?.
Related
function func(x = y, y = 2) {
return [x, y];
}
func(); // ReferenceError: y is not defined
func(1); // [1, 2]
As the code above implied, there is a hidden TDZ in the function parameters scope, which explains why the code below fails:
function func(arg) {
let arg = 1; // SyntaxError: Identifier 'arg' has already been declared
}
So function parameters should be declared with let, but what confused me is :
function func(arg) {
var arg = 1;
console.log(arg); // 1
}
this code works fine.
Why you can redeclare variables using var? How does Javascript declare function parameters?
edit:
I know exactly you can't use let to redeclare a variable. The question here is if function parameters is declared using let, so:
function func(arg) {
var arg = 1;
}
is like:
let arg; // arg parameter declares here
var arg = 1; // func body
and why this can run without an exception?
there is a hidden TDZ in the function parameters scope
Yes indeed. Have a look here for some more examples.
How does Javascript declare function parameters?
As parameters - see here for a step-by-step explanation. They're neither the same as let nor the same as var, they have their own semantics. ES6, which introduced default initialisers, gave them the same TDZ limitations as in let to catch more programmer mistakes.
Why you can redeclare variables using var?
Because until ES5, redeclaring a variable was not an error condition, and this behaviour needed to be preserved to not break the web. It could only be introduced for new features, like let and const - or argument lists that use default initialisers, try function x(bar, bar) {} vs function x(bar, bar=1){}.
This isn't necessarily related to the function's arguments.
This will throw an error as well:
function func() {
var arg = 1;
let arg = 2
console.log(arg); // 1
}
func();
You just can't use let to re-declare a variable, while var isn't that strict: var doesn't try to re-declare the variable if it's already declared. It just assigns the new value.
For more information on the specifics of let, see this MDN page on the let statement.
The reason that is, is because of something that people in the JavaScript community refer to as "hoisting" when you do var arg = 1 that gets translated into var arg; arg = 1; however if var has already been defined as is the case here because of the function parameter, then the compiler tries to be helpful and ignores subsequent re-declarations instead of throwing an error... which is probably an unexpected behaviour, and hasn't been followed through with the new let variable declaration, however, it has to stay there for backwards compatibility reasons.
So var is legacy syntax and ideally you probably want to avoid using it.
Instead using let for variables that can be reassigned, and const for constants.
var also has global scope where as let and const are limited to the scope they are in.
let is added more recently and with it, an error throw was introduced that manifests when you try to declare a variable that involves let twice. This does not happen with var which will simply replace the old value, unless the variable has been already declared with let.
function func(arg) {
let arg = 1; // not ok, because `let` knows that `arg` is being redeclared
}
function func(arg) {
var arg = 1; // ok, because `var` will just replace the value of `arg`
}
function func() {
let arg = 0;
var arg = 1; // not ok, because `let` declared `arg` already
}
Here is a link to the MDN for let which explains the feature.
Parameter bindings does not exactly follow the same behavior as the ones declared with let.
1) Parameter bindings get created before var bindings. var doesn't create bindings that already exist, so there's never a problem there. let however creates bindings after var.
2) There's an early error on all productions that create a scope that has it as a rule that no lexical binding can occur when there's already a variable declaration with the same name.
The first example will not throw an error a value if an argument is passed to func call.
var does not have the same scope as let or const.
Recently I ran into this weird thing in chrome console. Here I am intentionally assigning an undefined thing to a in order to throw an error.
let a = werwr // Uncaught ReferenceError: werwr is not defined
Then when I tried to assign something legit to a, this happened:
let a = "legit string" // Uncaught SyntaxError: Identifier 'a' has already been declared
so I can't use "let" because a has already been declared. So I tried to reassign something else to the "already declared a"
a = "legit string" // Uncaught ReferenceError: a is not defined
So it seems like I can't reassign something else to a but at the same time, a has been declared so I can't use let again.
I understand the difference between declaring and assigning a variable. However here it seems that neither could be done again. Does this has something to do with the scope of "let" in console? Because the same thing totally works for "var"
var a = werwr
// Uncaught ReferenceError: werwr is not defined
a = ”legit string“
// ”legit string“
var a = "legit string"
// Uncaught SyntaxError: Identifier 'a' has already been declared
Follow-up
There seem to be some difference between "manually" hoisting the let statement vs the implicit case.
throw new Error
let example = 5
// same errors as before
while in this case example can be reassigned again.
let example
throw new Error
example = 5
This happens when you introduce the temporal dead zone to the global scope. As you might know, let declarations are hoisted but left uninitialised. Due to control flow, it can happen that a variable is never initialised:
function …() {
if (false)
example; // would throw a ReferenceError if it was evaluated
… // do something
if (true)
return; // stop!
let example = 5; // never executed
}
This is fine in a function scope. Maybe something went wrong, maybe the variable wasn't needed at all - in the next call, a new scope with a new variable will be created.
A similar thing can happen in the global scope, when you throw an exception before the variable is initialised (only exceptions work here as a control flow construct, nothing else achieves the same effect).
throw new Error;
let example = 5;
In contrast to the function scope, it does matter here that the variable stays uninitialised. The global scope lasts forever, and the variable is eternally dead. It was not and will never be initialised, and lexical variables cannot be re-declared (which helps preventing mistakes).
This was discussed on es-discuss, but deemed irrelevant. If top-level <script> execution throws an error, you have bigger problems than uninitialised variables. There is no path to recover. If you need one (e.g. by trying to re-declare it in successive scripts), you have to use var anyway.
That you have the same problem in the devtools console is a bit of a nuisance, but could be solved for the console as a special scope.
You should know about hoisting in JS. Basically, a declaration like this
let a = werwr;
is interpreted as
let a;
a = werwr;
And that why a is already declared when you run your second line of code.
UPDATE
So, there is a NOTE in ES specs
https://tc39.github.io/ecma262/#prod-LetOrConst
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.
...
"but may not be accessed in any way until the variable's LexicalBinding is evaluated"
means that, the declaration must be successfully completed before you can access the variable (either getting value, assigning value, or doing typeof, or event delete);
In your case, the variable's LexicalBinding is interupted by the exception.
let a = werwr // Uncaught ReferenceError: werwr is not defined
Please follow the link and read more about that. If you find a way to recover varaible a, please tell me.
Thanks, today I found out something new about Javascript.
In the following code:
var greeting = "hi";
function changeGreeting() {
if (greeting == "hi") {
var greeting = "hello";
}
alert(greeting);
}
changeGreeting();
...greeting is undefined. However if I remove the var and change changeGreeting() to this:
function changeGreeting() {
if (greeting == "hi") {
greeting = "hello";
}
alert(greeting);
}
...I get "hello" as expected.
I would never redeclare a variable like this in my code, but why does this happen?
JavaScript variables have function scope. Thus, the very presence of var greeting inside the function will declare a local greeting variable, which will be undefined at the time of its mention in if condition: the global variable will not be visible inside the function, being overshadowed by the local one. Therefore, the if does not happen, the assignment to hello doesn't happen, the variable is still undefined.
In the second example, you're using the global variable throughout, it is not overshadowed by a local variable (because, no var greeting inside the function), and things work as you expect.
It's very simple: JS hoist the Variable declarations to the top of the current scope, but any operations (including assignments) aren't hoisted (within the same scope, see second case explanation), of course. So your snippet is translated to
(function()
{
var currentSize;//undefined
if (currentSize == 'hi')//always false
{
currentSize = 'hello';//assignment that will never be
}
alert(currentSize);//alerts undefined, of course
}());
By leaving out the var, proceeds to scope scanning (checking for the variable being declared in the global scope). Sadly, in doing so, the context of the first use of the var is lost (inside a branch), and the assignment is hoisted, too. An implied global is translated to:Thank god this isn't true. I assumed it was, because I tested a couple of things in the console that seemed to corroborate this. In this case #amadan is right: you're using the global variable (called greeting in your snippet by mistake when I posted this). I'm going to leave the code below (corrected it) to show what implied globals actually are, hoping it helps somebody sometime in understanding scopes/scope-scanning in JS.
var currentSize = 'hello';
//well, actually implied globals can be deleted, so it's more like
Object.defineProperty(this,'currentSize',{value:undefined,
writable:true,
enumerable:true,
configurable:true});
(function()
{
if (currentSize == 'hi')//always false
{//this still doesn't get executed
currentSize = 'hello';//assignment that will never be
}
alert(currentSize);//alerts undefined
}());
In your first code snippet, you're checking global variable, which doesn't exist -> doesn't pass if condition.
Check out this on javascript scopes and how to work with variables Javascript garden - function scopes
Say I'm trying to execute this JavaScript snippet. Assume the undeclared vars and methods are declared elsewhere, above, and that something and somethingElse evaluate to boolean-true.
try {
if(something) {
var magicVar = -1;
}
if(somethingElse) {
magicFunction(magicVar);
}
} catch(e) {
doSomethingWithError(e);
}
My question is: what is the scope of magicVar and is it okay to pass it into magicFunction as I've done?
Lots of other good answers about how Javascript handles this with var, but I thought I'd address the let situation...
If a variable is defined with let inside the try block, it will NOT be in scope inside the catch (or finally) block(s). It would need to be defined in the enclosing block.
For example, in the following code block, the console output will be "Outside":
let xyz = "Outside";
try {
let xyz = "Inside";
throw new Error("Blah");
} catch (err) {
console.log(xyz);
}
Javascript has function scope. That means that magicvar will exist from the beginning of the function it's declared in all the way to the end of that function, even if that statement doesn't ever execute. This is called variable hoisting. The same thing happens with functions declarations, which in turn is called function hoisting.
If the variable is declared in global scope, it will be visible to everything. This is part of the reason why global variables are considered evil in Javascript.
Your example will pass undefined into magicFunction if something is false, because magicVar hasn't been assigned to anything.
While this is technically valid Javascript, it's generally considered bad style and will not pass style checkers like jsLint. Extremely unintuitive Javascript like this will execute without any error
alert(a); //alerts "undefined"
var a;
POP QUIZ: What does the following code do?
(function() {
x = 2;
var x;
alert(x);
})();
alert(x);
In javascript only functions create a new context -closure.
Every definition of a variable is really a declaration of the variable at the top of its scope and an assignment at the place where the definition is.
var
function-scoped
hoist to the top of its function
redeclarations of the same name in the same scope are no-ops
You may want to read MDN scope cheat sheet
Due to hoisting You can even do things like this:
function bar() {
var x = "outer";
function foo() {
alert(x); // {undefined} Doesn't refer to the outerscope x
// Due the the var hoising next:
x = 'inner';
var x;
alert(x); // inner
}
foo();
}
bar();
bar();
Demo
So the foo function is converted to something like this:
function foo() {
var x;
alert(x); // {undefined} Doesn't refer to the outerscope x
// Due the the var hoising next:
x = 'inner';
alert(x); // inner
}
My question is: what is the scope of magicVar and is it okay to pass it into magicFunction as I've done?
Define okay..., Yes the code is valid, but it's less readable then if the variables declarations were on the top, that's all.
Due to javascript "hoisting" (MDN description), your variable declaration code gets translated as:
function yourFunction() {
var magicVar;
try {
if(something) {
magicVar = -1;
}
if(somethingElse) {
magicFunction(magicVar);
}
} catch(e) {
doSomethingWithError(e);
}
} //end of your function
"Hoisting" moves all variables declarations to the top of the function. So magicVar is available everywhere in the function, but it's undefined until you give it a value.
Your variable has function scope.
With var, variables exist from the beginning of the function to the end of it, no matter where they are declared, or even if the statement is actually ever reached. They will, however, be undefined until they are assigned another value.
So in your case, if something is false but somethingelse is true, you will call magicFunction with its first argument being undefined.
The let keyword, created in Javascript 1.9 and available (as of today, May 3rd 2012, and as far as I know) only in Firefox, declares variables with the scoped semantics you're probably used to.
I agree with variable hoisting and function hoisting, I would like to emphasis two import points.
Identifier Defined in Catch parameter is i.e. err/e(error) , is scoped to Catch defined block.
Function first hoisting.
example :
b(); // output : 3
var b = 2;
function b(){
return 3;
}
Today, I got completely surprised when I saw that a global variable has undefined value in a certain case.
Example:
var value = 10;
function test() {
//A
console.log(value);
var value = 20;
//B
console.log(value);
}
test();
Gives output as
undefined
20
Here, why is the JavaScript engine considering global value as undefined? I know that JavaScript is an interpreted language. How is it able to consider variables in the function?
Is that a pitfall from the JavaScript engine?
This phenomenon is known as: JavaScript Variable Hoisting.
At no point are you accessing the global variable in your function; you're only ever accessing the local value variable.
Your code is equivalent to the following:
var value = 10;
function test() {
var value;
console.log(value);
value = 20;
console.log(value);
}
test();
Still surprised you're getting undefined?
Explanation:
This is something that every JavaScript programmer bumps into sooner or later. Simply put, whatever variables you declare are always hoisted to the top of your local closure. So, even though you declared your variable after the first console.log call, it's still considered as if you had declared it before that.
However, only the declaration part is being hoisted; the assignment, on the other hand, is not.
So, when you first called console.log(value), you were referencing your locally declared variable, which has got nothing assigned to it yet; hence undefined.
Here's another example:
var test = 'start';
function end() {
test = 'end';
var test = 'local';
}
end();
alert(test);
What do you think this will alert? No, don't just read on, think about it. What's the value of test?
If you said anything other than start, you were wrong. The above code is equivalent to this:
var test = 'start';
function end() {
var test;
test = 'end';
test = 'local';
}
end();
alert(test);
so that the global variable is never affected.
As you can see, no matter where you put your variable declaration, it is always hoisted to the top of your local closure.
Side note:
This also applies to functions.
Consider this piece of code:
test("Won't work!");
test = function(text) { alert(text); }
which will give you a reference error:
Uncaught ReferenceError: test is not defined
This throws off a lot of developers, since this piece of code works fine:
test("Works!");
function test(text) { alert(text); }
The reason for this, as stated, is because the assignment part is not hoisted. So in the first example, when test("Won't work!") was run, the test variable has already been declared, but has yet to have the function assigned to it.
In the second example, we're not using variable assignment. Rather, we're using proper function declaration syntax, which does get the function completely hoisted.
Ben Cherry has written an excellent article on this, appropriately titled JavaScript Scoping and Hoisting.
Read it. It'll give you the whole picture in full detail.
I was somewhat disappointed that the problem here is explained, but no one proposed a solution. If you want to access a global variable in function scope without the function making an undefined local var first, reference the var as window.varName
Variables in JavaScript always have function-wide scope. Even if they were defined in the middle of the function, they are visible before. Similar phenomena may be observed with function hoisting.
That being said, the first console.log(value) sees the value variable (the inner one which shadows the outer value), but it has not yet been initialized. You can think of it as if all variable declarations were implicitly moved to the beginning of the function (not inner-most code block), while the definitions are left on the same place.
See also
Javascript function scoping and hoisting
Javascript variable declarations at the head of a function
There is a global variable value, but when control enters the test function, another value variable is declared, which shadows the global one. Since variable declarations (but not assignments) in JavaScript are hoisted to the top of scope in which they are declared:
//value == undefined (global)
var value = 10;
//value == 10 (global)
function test() {
//value == undefined (local)
var value = 20;
//value == 20 (local)
}
//value == 10 (global)
Note that the same is true of function declarations, which means you can call a function before it appears to be defined in your code:
test(); //Call the function before it appears in the source
function test() {
//Do stuff
}
It's also worth noting that when you combine the two into a function expression, the variable will be undefined until the assignment takes place, so you can't call the function until that happens:
var test = function() {
//Do stuff
};
test(); //Have to call the function after the assignment
The simplest way to keep access to outer variables (not just of global scope) is, of course, to try to not re-declare them under the same name in functions; just do not use var there. The use of proper descriptive naming rules is advised. With those, it will be hard to end up with variables named like value (this aspect is not necessarily related to the example in the question as this variable name might have been given for simplicity).
If the function might be reused elsewhere and hence there is no guarantee that the outer variable actually defined in that new context, Eval function can be used. It is slow in this operation so it is not recommended for performance-demanding functions:
if (typeof variable === "undefined")
{
eval("var variable = 'Some value';");
}
If the outer scope variable you want access to is defined in a named function, then it might be attached to the function itself in the first place and then accessed from anywhere in the code -- be it from deeply nested functions or event handlers outside of everything else. Notice that accessing properties is way slower and would require you to change the way you program, so it is not recommended unless it is really necessary: Variables as properties of functions (JSFiddle):
// (the wrapper-binder is only necessary for using variables-properties
// via "this"instead of the function's name)
var functionAsImplicitObjectBody = function()
{
function someNestedFunction()
{
var redefinableVariable = "redefinableVariable's value from someNestedFunction";
console.log('--> functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
console.log('--> redefinableVariable: ', redefinableVariable);
}
var redefinableVariable = "redefinableVariable's value from someFunctionBody";
console.log('this.variableAsProperty: ', this.variableAsProperty);
console.log('functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
console.log('redefinableVariable: ', redefinableVariable);
someNestedFunction();
},
functionAsImplicitObject = functionAsImplicitObjectBody.bind(functionAsImplicitObjectBody);
functionAsImplicitObjectBody.variableAsProperty = "variableAsProperty's value, set at time stamp: " + (new Date()).getTime();
functionAsImplicitObject();
// (spread-like operator "..." provides passing of any number of arguments to
// the target internal "func" function in as many steps as necessary)
var functionAsExplicitObject = function(...arguments)
{
var functionAsExplicitObjectBody = {
variableAsProperty: "variableAsProperty's value",
func: function(argument1, argument2)
{
function someNestedFunction()
{
console.log('--> functionAsExplicitObjectBody.variableAsProperty: ',
functionAsExplicitObjectBody.variableAsProperty);
}
console.log("argument1: ", argument1);
console.log("argument2: ", argument2);
console.log("this.variableAsProperty: ", this.variableAsProperty);
someNestedFunction();
}
};
return functionAsExplicitObjectBody.func(...arguments);
};
functionAsExplicitObject("argument1's value", "argument2's value");
I was running into the same problem even with global variables. My problem, I discovered, was global variable do Not persist between html files.
<script>
window.myVar = 'foo';
window.myVarTwo = 'bar';
</script>
<object type="text/html" data="/myDataSource.html"></object>
I tried to reference myVar and myVarTwo in the loaded HTML file, but received the undefined error.
Long story/day short, I discovered I could reference the variables using:
<!DOCTYPE html>
<html lang="en">
<!! other stuff here !!>
<script>
var myHTMLVar = this.parent.myVar
/* other stuff here */
</script>
</html>