159

I can't understand why variables act so strange when declared inside a function.

  1. In the first function I declare with let the variables b and c with the value 10:

    b = c = 10;
    

    In the second function I show:

    b + ", " + c
    

    And this shows:

    10, 10
    
  2. Also in first function I declare a with value 10:

    let a = b = c = 10;
    

    But in the second function it shows an error:

    Can't find variable: a

  3. Now in the first function I declare d with value 20:

    var d = 20;
    

    But in the second function it shows the same error as before, but with the variable d:

    Can't find variable: d

Example:

function first() {
  let a = b = c = 10;
  var d = 20;
  second();
}

function second() {
  console.log(b + ", " + c); //shows "10, 10"

  try{ console.log(a); }  // Rreference error
  catch(e){ console.error(e.message) }

  try{ console.log(d); } // Reference error
  catch(e){ console.error(e.message) }
}
first()
ArtEze
  • 186
  • 1
  • 5
  • 17
bleat interteiment
  • 1,429
  • 2
  • 10
  • 15
  • 31
    You're declaring globals, since `b` and `c` are not prefixed with the `var` keyword. `a` and `d` are local to `first`. – VLAZ Jan 27 '20 at 13:02
  • 1
    Comments are not for extended discussion; a tangential conversation about whether this would be a good interview question has been [archived in chat](https://chat.stackoverflow.com/rooms/206887/discussion-on-question-by-bleat-interteiment-why-do-some-variables-declared-usin). – Cody Gray - on strike Jan 29 '20 at 22:48
  • 1
    This reminds me of a similar situation in Visual Basic; `Dim Apple, Banana, Pear As Fruit` means `Dim Apple / Dim Banana / Dim Pear As Fruit`, and not `Dim Apple As Fruit / ...`. – Eric Lippert Jan 30 '20 at 20:03

8 Answers8

180

It's because you're actually saying:

c = 10;
b = c;
let a = b;

And not what you think you are saying, which is:

let a = 10;
let b = 10;
let c = 10;

You'll notice that no matter how many variables you add to your chain, it will only be the first (a) that causes the error.

This is because "let" scopes your variable to the block (or, "locally", more or less meaning "in the brackets") in which you declare it.

If you declare a variable without "let", it scopes the variable globally.

So, in the function where you set your variables, everything gets the value 10 (you can see this in the debugger if you put a breakpoint). If you put a console log for a,b,c in that first function, all is well.

But as soon as you leave that function, the first one (a)--and again, keep in mind, technically in the order of assignment, it is the last one-- "disappears" (again, you can see this in the debugger if you set a breakpoint in the second function), but the other two (or however many you add) are still available.

This is because, "let" ONLY APPLIES TO (so only locally scopes) THE FIRST VARIABLE--again, which is technically the last to be declared and assigned a value--in the chain. The rest technically do not have "let" in front of them. So those are technically declared globally (that is, on the global object), which is why they appear in your second function.

Try it: remove the "let" keyword. All your vars will now be available.

"var" has a similar local-scope effect, but differs in how the variable is "hoisted", which is something you should definitely understand, but which is not directly involved with your question.

(BTW, this question would stump enough pro JS devs to make it a good one).

Strongly suggest you spend time with the differences in how variables can be declared in JS: without a keyword, with "let", and with "var".

Tim Consolazio
  • 4,802
  • 2
  • 19
  • 28
  • It might be worth mentioning `const` - similar semantics to `let` declarations, and generally a better idea to use if you care about multiple assignments – Delioth Jan 28 '20 at 19:00
  • That's sort of out of scope here methinks (pun intended). – Tim Consolazio Jan 28 '20 at 21:46
  • That's why I strongly recommend to forget the newish `let` declaration and to return to old good `var`. – Thevs Jan 28 '20 at 21:51
  • 5
    It is simultaneously the best and worst thing about programming: the computer will do *exactly* what you tell it to do. Not necessarily what you intended to tell it to do. Programs are perfect. We create the problems. – Niet the Dark Absol Jan 28 '20 at 22:30
  • 8
    @Thevs Why do you recommend `var` over `let` in the context of this answer? I don't understand. – Klaycon Jan 28 '20 at 22:45
  • 4
    @Thevs I strongly disagree with you. `var` can be prone to bugs if used carelessly. Check this [fiddle](https://jsfiddle.net/gfo09w5z/) – Cid Jan 29 '20 at 08:21
  • 2
    @Thevs in what case does `var` have *any* advantage over `let`? Let me clarify: a modern context when both are options and I'm asking for code that one *should* write. When I've asked this before, I've received answers about "you can re-declare a variable with `var`" in which case I have to remind people that *you shouldn't be re-declaring variables*. That's either a bug or an error in the logic of the code - so the benefit of re-declaration is...that it allows you to write faulty code. I've yet to see any sensible reason in favour for `var` when `let` is also an option. – VLAZ Jan 29 '20 at 08:55
  • 2
    Chalk up another mark against javascript. All variables are global unless declared local. :( – JRE Jan 29 '20 at 14:01
  • I don't consider that a mark against. It's just a characteristic of it. As for "var" vs. "let", that depends. Many style guides (including Google's) recommend declaring variables close to the usage, not just all at the top of the function. As other developers work with the code, if you exclusively use "let", this can create some interesting to debug issues. If you use "var", the variables are all "hoisted" to the top of the procedure, so you don't end up with temporal dead zones. The pros and cons are predictable enough, myself, I acknowledge the option. "Never" is dicey in programming. – Tim Consolazio Jan 29 '20 at 14:13
  • @TimConsolazio That is exactly one of the pitfalls of `var` specifically. Like VLAZ was explaining, `var` lets you re-declare variables. Combined with hoisting, this means that if you follow the style guide and declare variables close to usage (presumably within nested blocks, loops, statements, etc) and inadvertently re-use a variable name, it will be the *same variable* and you will end up modifying it without an intent to. If you want to declare variables at the top of the function, just do so with `let` and avoid potential pitfalls. – Klaycon Jan 29 '20 at 15:55
  • I tend to agree with what you say, but my own preferences and inclinations aside, I understand the option and have seen it used wisely more than once. – Tim Consolazio Jan 29 '20 at 16:10
  • Have used for awhile a question like this on interviews, as it feels like it's something an experienced developer we either recognize straight away, or can figure out after giving it some thought. Not sure though about the "this would stump professional javascript developers"... but yeah, definitely one of the uglier parts of javascript. – David Mulder Jan 30 '20 at 13:46
  • @NiettheDarkAbsol To be fair, well designed languages try to let you only do things that actually do what the programmer probably intends (which is to say, they try to avoid ambiguous syntax). It's just that javascript has never been well designed... – Harabeck Jan 30 '20 at 19:27
  • That's a matter for debate. While I acknowledge that JS has its foibles, that's largely due to the fact that the language exploded to a level of use that was unforeseen; it's evolved unbelievably over a relatively short period of time which probably would not have been (and continue to be) possible if the core design was outright broken. That said though, it has held up amazingly well over time and that design has proved amazingly flexible. I enjoy working with it a great deal more than say, Java. OOP people hate it, functional people love it, on and on the debate goes. – Tim Consolazio Jan 30 '20 at 19:39
  • @Tim Consolazio Javascript was existing more than 20 years with only `var` declaration. Does it mean something? I prefer `var` way for declarations, because it clearly scopes to the function. `let` clause just breaks this rule. I don't need any scopes other than function. If someone is afraid of re-declaration - it means he is not good at programming at all. It's very easy to get used to variables names in the project, Even more - you can just use namespaces to make naming more clear. – Thevs Feb 08 '20 at 11:05
  • @Klaycon Just like arrow functions kills the idea of `this`, the `let` clause kills the idea of function scoping and globals. – Thevs Feb 08 '20 at 22:24
  • @Thevs I tend to agree: one person's reason for saying "this is wrong/bad", is not a universal reason to say that thing is wrong/bad. Far too often I see programmers say there is ONE RIGHT WAY to do something and/or think (that being, "the way I learned/prefer/think"). These religious bastions, to me, are a waste of energy. JS in the right hands is elegant. Java in the wrong hands is a car fire. Type systems, scoping mechanics, nothing saves code from a dev that doesn't grok the language and has decided there's only one way to think about programming. – Tim Consolazio Feb 09 '20 at 19:17
  • One thing I will say though,with the modern nature of programming teams (distributed developers that come and go), a language that is supremely flexible can be problematic; this is the problem I think TS really addresses (I otherwise think that TS being "better" than JS is overstated). The rigidity of type systems and standardization of scoping mechanics forces teams to code more predictably....note that I do not say "better". Things like TS do not prevent writing terrible code. Back in the day, JS devs got "cute" with scoping etc. Now it's "how can I use generics and mixins to look smart?" – Tim Consolazio Feb 09 '20 at 19:29
  • @TimConsolazio: "it's evolved unbelievably over a relatively short period of time" Nope. Javascript has developed [over the last 25 years.](https://en.wikipedia.org/wiki/JavaScript) nothing sudden about it. It was as ugly and disorganized then as it is now. – JRE Feb 20 '20 at 12:45
  • Yep. Twenty five years is not a long period of time for a language to evolve from a practically useless and unstable scripting toy, to the most important single ecosystem on the web, server side and all. I have been working with the tech since day one (and before). I’ve seen them all come and go. As far as ugly and disorganized, I typically find that opinion to be a matter of personal disposition and experience. I am a student of Crockford and never had the issues your typical OOP developer laments. Myself, I find Java, for example, bloated and ponderous, I greatly prefer Scala. – Tim Consolazio Feb 21 '20 at 12:51
70

In the function first(), variables band c are created on the fly, without using var or let.

let a = b = c = 10; // b and c are created on the fly

Is different than

let a = 10, b = 10, c = 10; // b and c are created using let (note the ,)

They become implicit global. That's why they are available in second()

From documentation

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.

To avoid this, you can use "use strict" that will provide errors when one use an undeclared variable

"use strict"; // <-------------- check this

function first() {
   /*
    * With "use strict" c is not defined.
    * (Neither is b, but since the line will be executed from right to left,
    * the variable c will cause the error and the script will stop)
    * Without, b and c become globals, and then are accessible in other functions
    */
   let a = b = c = 10;
   var d = 20;
   second();
}

function second() {
   console.log(b + ", " + c); //reference error
   console.log(a); //reference error
   console.log(d); //reference error
}

first();
Cid
  • 14,968
  • 4
  • 30
  • 45
  • 16
    In addition: `let a = 10, b = 10, c = 10;` or `let a, b, c; a = b = c = 10;` would otherwise be correct way of declaring the variables. – Rickard Elimää Jan 27 '20 at 13:07
  • So with the strict mode, what about the variable b? – Tinu Jos K Jan 27 '20 at 13:22
  • 2
    @Tick20 the variable `b` won't be evaluated/reached, the error will occur on the line `let a = b = c = 10;`, read from **right to left**. `c` being the first variable causing `ReferenceError`, the remaining of the line won't be executed (the script has stopped) – Cid Jan 27 '20 at 13:23
  • 2
    something like `let a = 10, b = a, c = b;` is valid too – Kaddath Jan 28 '20 at 09:18
  • 8
    upvoted mainly for the "use strict". In the context of an interview question, that would also be also the start of my comment on this code. – Pac0 Jan 28 '20 at 16:10
24

Before calling things strange, let’s know some basics first:

var and let are both used for variable declaration in JavaScript. For example,

var one = 1;
let two = 2;

Variables can also be declared without using var or let. For example,

three = 3;

Now the difference between the above approaches is that:

var is function scoped

and

let is block scoped.

while the scope of the variables declared without var/let keyword become global irrespective of where it is declared.

Global variables can be accessed from anywhere in the web page (not recommended because globals can be accidentally modified).

Now according to these concepts let's have a look at the code in question:

 function first() {
   let a = b = c = 10;
   /* The above line means:
    let a=10; // Block scope
    b=10; // Global scope
    c=10; // Global scope
    */

   var d = 20; // Function scope
   second();
}

function second() {
   alert(b + ", " + c); // Shows "10, 10" //accessible because of global scope
   alert(a); // Error not accessible because block scope has ended
   alert(d); // Error not accessible because function scope has ended
}
fatimasajjad
  • 605
  • 8
  • 16
  • 1
    You plagiarised part of [JonoJames's answer](https://stackoverflow.com/questions/59931668/why-do-variables-inside-functions-act-so-strange/59932487#59932487). Why? – Peter Mortensen Jan 28 '20 at 18:39
  • 2
    I'm sorry but i had no such intent, something similar might be there because we might have collected a piece of information from the same source. – fatimasajjad Jan 29 '20 at 06:29
  • 2
    It may be that both answers contain content copied from the same original source without apparent citation -- possibly https://www.tutorialsteacher.com/javascript/javascript-variable. The presence of plagiarism is apparent, since a grammatical error is reproduced from the original -- *"the scope of variables declared without the `var` keyword become global irrespective of where it is declared"* should either be *"the scope... becomes"* or *"the scopes... become"*. Using someone else's exact words requires citation, whether from here or elsewhere. https://meta.stackexchange.com/q/160071/211183 – Michael - sqlbot Jan 30 '20 at 20:04
  • Thanks guys, i have added a reference link for the source. – fatimasajjad Jan 31 '20 at 07:15
6

Variables using the let keyword should only be available within the scope of the block and not available in an outside function...

Each variable that you are declaring in that manner is not using let or var. You are missing a comma in the variables declaration.

It is not recommended to declare a variable without the var keyword. It can accidentally overwrite an existing global variable. The scope of the variables declared without the var keyword become global irrespective of where it is declared. Global variables can be accessed from anywhere in the web page.

function first() {
   let a = 10;
   let b = 10;
   let c = 10;
   var d = 20;
   second();
}

function second() {
   console.log(b + ", " + c); //shows "10, 10"
   console.log(a); //reference error
   console.log(d); //reference error
}

first();
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
JonoJames
  • 1,117
  • 8
  • 22
3

It's because of when you don't use let or var then variable is getting declare on the fly, better you declare like following.

let a = 10;
let b = 10;
let c = 10;
Mohit Rathod
  • 1,057
  • 1
  • 19
  • 33
3

The strange issue is caused by scoping rules in JavaScript

function first() {
   let a = b = c = 10; // a is in local scope, b and c are in global scope
   var d = 20; // d is in local scope
   second(); // will have access to b and c from the global scope
}

Assuming that you want to declare 3 local variables initialised to the same value (100). Your first() will look like below. In this case, second() will not have access to any of the variables because they are local to first()

function first() {
   let a = 100; // a is in local scope init to 100
   let b = a; // b is in local scope init to a
   let c = b // c is in local scope init to b

   var d = 20; // d is in local scope
   second(); // will not have access a, b, c, or d
}

However, if you want global variables then your first() will look like below. In this case, second will have access to all the variables because they are in global scope

function first() {
   a = 100; // a is in global scope
   b = a; // b is in global scope
   c = b // c is in global scope

   d = 20; // d is in global scope
   second(); // will have access to a, b, c, and d from the global scope
}

Local variables (aka. accessible in the code block where they are declared).
A Code block is any {} with line(s) of code between.

  • function() {var, let, const in here is accessible to entire function},
  • for() {var in here is accessible to outer scope, let, const accessible only in here},
  • etc.

Global variables (aka accessible in the global scope).
These variables are attached to the global object. The global object is environment dependent. It is the window object in browsers.

Special note: You can declare variables in JavaScript without using the var, let, const keywords. A variable declared this way is attached to the global object, therefore accessible in the global scope.
a = 100 // is valid and is in global scope

Some articles for further reading: https://www.sitepoint.com/demystifying-javascript-variable-scope-hoisting/ https://scotch.io/tutorials/understanding-scope-in-javascript https://www.digitalocean.com/community/tutorials/understanding-variables-scope-hoisting-in-javascript

Funwie
  • 91
  • 4
1

Main difference is scoping rules. Variables declared by var keyword are scoped to the immediate function body (hence the function scope) while let variables are scoped to the immediate enclosing block denoted by { } (hence the block scope). And when you say

c = 10;
b = c;
let a = b;

c and b have the life span as fun have but a only have block span and if you try to access a by referencing it always show error but c and b are globally so they don't.You'll notice that no matter how many variables you add to your chain, it will only be the first (a) that causes the error.This is because "let" scopes your variable to the block (or, "locally", more or less meaning "in the brackets") in which you declare it.If you declare a variable without "let", it scopes the variable globally.So, in the function where you set your variables, everything gets the value 10 (you can see this in the debugger if you put a break-point). If you put a console log for a,b,c in that first function, all is well.But as soon as you leave that function, the first one (a)--and again, keep in mind, technically in the order of assignment, it is the last one-- "disappears" (again, you can see this in the debugger if you set a break-point in the second function), but the other two (or however many you add) are still available.

1

Here are the 3 interesting aspects of variable declarations in JavaScript:

  1. var restricts the scope of variable to the block in which it is defined. ('var' is for local scope.)

  2. let allows temporary overriding of an external variable's value inside a block.

  3. Simply declaring a variable without var or let will make the variable global, regardless of where it is declared.

Here is a demo of let, which is the latest addition to the language:

// File name:  let_demo.js

function first() {
   a = b = 10
   console.log("First function:    a = " + a)
   console.log("First function:    a + b = " + (a + b))
}

function second() {
    let a = 5
    console.log("Second function:    a = " + a)
    console.log("Second function:    a + b = " + (a + b))
}

first()   

second()

console.log("Global:    a = " + a)
console.log("Global:    a + b = " + (a + b))

Output:

$ node let_demo.js 

First function:    a = 10
First function:    a + b = 20

Second function:    a = 5
Second function:    a + b = 15

Global:    a = 10
Global:    a + b = 20

Explanation:

The variables a and b were delcared inside 'first()', without var or let keywords.

Therefore, a and b are global, and hence, are accessible throughout the program.

In function named 'second', the statement 'let a = 5' temporarily sets the value of 'a' to '5', within the scope of the function only.

Outside the scope of 'second()', I.E., in the global scope, the value of 'a' will be as defined earlier.

Gopinath
  • 4,066
  • 1
  • 14
  • 16