What is the difference between "let" and "var"?
Solution 1
Scoping rules
The 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).
function run() { var foo = "Foo"; let bar = "Bar";
console.log(foo, bar); // Foo Bar
{ var moo = “Mooo” let baz = “Bazz”; console.log(moo, baz); // Mooo Bazz }
console.log(moo); // Mooo console.log(baz); // ReferenceError }
run();
The reason why let
keyword was introduced to the language was function scope is confusing and was one of the main sources of bugs in JavaScript.
Take a look at this example from another Stack Overflow question:
var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
My value: 3
was output to console each time funcs[j]();
was invoked since anonymous functions were bound to the same variable.
People had to create immediately invoked functions to capture correct values from the loops but that was also hairy.
Hoisting
Variables declared with var
keyword are hoisted and initialized which means they are accessible in their enclosing scope even before they are declared, however their value is undefined
before the declaration statement is reached:
function checkHoisting() { console.log(foo); // undefined var foo = "Foo"; console.log(foo); // Foo }
checkHoisting();
let
variables are hoisted but not initialized until their definition is evaluated. Accessing them before the initialization results in a ReferenceError
. The variable is said to be in the temporal dead zone from the start of the block until the declaration statement is processed.
function checkHoisting() { console.log(foo); // ReferenceError let foo = "Foo"; console.log(foo); // Foo }
checkHoisting();
Creating global object property
At the top level, let
, unlike var
, does not create a property on the global object:
var foo = "Foo"; // globally scoped let bar = "Bar"; // globally scoped but not part of the global object
console.log(window.foo); // Foo console.log(window.bar); // undefined
Redeclaration
In strict mode, var
will let you re-declare the same variable in the same scope while let
raises a SyntaxError.
'use strict'; var foo = "foo1"; var foo = "foo2"; // No problem, 'foo1' is replaced with 'foo2'.
let bar = “bar1”; let bar = “bar2”; // SyntaxError: Identifier ‘bar’ has already been declared
Solution 2
let
can also be used to avoid problems with closures. It binds fresh value rather than keeping an old reference as shown in examples below.
for(var i=1; i<6; i++) {
$("#div" + i).click(function () { console.log(i); });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>Clicking on each number will log to console:</p>
<div id="div1">1</div>
<div id="div2">2</div>
<div id="div3">3</div>
<div id="div4">4</div>
<div id="div5">5</div>
Code above demonstrates a classic JavaScript closure problem. Reference to the i
variable is being stored in the click handler closure, rather than the actual value of i
.
Every single click handler will refer to the same object because there’s only one counter object which holds 6 so you get six on each click.
A general workaround is to wrap this in an anonymous function and pass i
as an argument. Such issues can also be avoided now by using let
instead var
as shown in the code below.
(Tested in Chrome and Firefox 50)
for(let i=1; i<6; i++) {
$("#div" + i).click(function () { console.log(i); });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>Clicking on each number will log to console:</p>
<div id="div1">1</div>
<div id="div2">2</div>
<div id="div3">3</div>
<div id="div4">4</div>
<div id="div5">5</div>
Solution 3
What's the difference between let
and var
?
- A variable defined using a
var
statement is known throughout the function it is defined in, from the start of the function. (*) - A variable defined using a
let
statement is only known in the block it is defined in, from the moment it is defined onward. (**)
To understand the difference, consider the following code:
// i IS NOT known here // j IS NOT known here // k IS known here, but undefined // l IS NOT known here
function loop(arr) { // i IS known here, but undefined // j IS NOT known here // k IS known here, but has a value only the second time loop is called // l IS NOT known here
for( var i = 0; i < arr.length; i++ ) { // i IS known here, and has a value // j IS NOT known here // k IS known here, but has a value only the second time loop is called // l IS NOT known here }; // i IS known here, and has a value // j IS NOT known here // k IS known here, but has a value only the second time loop is called // l IS NOT known here for( let j = 0; j < arr.length; j++ ) { // i IS known here, and has a value // j IS known here, and has a value // k IS known here, but has a value only the second time loop is called // l IS NOT known here }; // i IS known here, and has a value // j IS NOT known here // k IS known here, but has a value only the second time loop is called // l IS NOT known here
}
loop([1,2,3,4]);
for( var k = 0; k < arr.length; k++ ) { // i IS NOT known here // j IS NOT known here // k IS known here, and has a value // l IS NOT known here };
for( let l = 0; l < arr.length; l++ ) { // i IS NOT known here // j IS NOT known here // k IS known here, and has a value // l IS known here, and has a value };
loop([1,2,3,4]);
// i IS NOT known here // j IS NOT known here // k IS known here, and has a value // l IS NOT known here
Here, we can see that our variable j
is only known in the first for loop, but not before and after. Yet, our variable i
is known in the entire function.
Also, consider that block scoped variables are not known before they are declared because they are not hoisted. You're also not allowed to redeclare the same block scoped variable within the same block. This makes block scoped variables less error prone than globally or functionally scoped variables, which are hoisted and which do not produce any errors in case of multiple declarations.
Is it safe to use let
today?
Some people would argue that in the future we'll ONLY use let statements and that var statements will become obsolete. JavaScript guru Kyle Simpson wrote a very elaborate article on why he believes that won't be the case.
Today, however, that is definitely not the case. In fact, we need actually to ask ourselves whether it's safe to use the let
statement. The answer to that question depends on your environment:
If you're writing server-side JavaScript code (Node.js), you can safely use the
let
statement.If you're writing client-side JavaScript code and use a browser based transpiler (like Traceur or babel-standalone), you can safely use the
let
statement, however your code is likely to be anything but optimal with respect to performance.If you're writing client-side JavaScript code and use a Node based transpiler (like the traceur shell script or Babel), you can safely use the
let
statement. And, because your browser will only know about the transpiled code, performance drawbacks should be limited.If you're writing client-side JavaScript code and don't use a transpiler, you need to consider browser support.
There are still some browsers that don't support
let
at all :
How to keep track of browser support
For an up-to-date overview of which browsers support the let
statement at the time of your reading this answer, see this Can I Use
page.
(*) Globally and functionally scoped variables can be initialized and used before they are declared because JavaScript variables are hoisted. This means that declarations are always moved to the top of the scope.
(**) Block scoped variables are not hoisted
Solution 4
Here's an explanation of the let
keyword with some examples.
let
works very much likevar
. The main difference is that the scope of avar
variable is the entire enclosing function
This table on Wikipedia shows which browsers support Javascript 1.7.
Note that only Mozilla and Chrome browsers support it. IE, Safari, and potentially others don't.
Solution 5
let
Block scope
Variables declared using the let
keyword are block-scoped, which means that they are available only in the block in which they were declared.
At the top level (outside of a function)
At the top level, variables declared using let
don't create properties on the global object.
var globalVariable = 42; let blockScopedVariable = 43;
console.log(globalVariable); // 42 console.log(blockScopedVariable); // 43
console.log(this.globalVariable); // 42 console.log(this.blockScopedVariable); // undefined
Inside a function
Inside a function (but outside of a block), let
has the same scope as var
.
(() => { var functionScopedVariable = 42; let blockScopedVariable = 43;
console.log(functionScopedVariable); // 42 console.log(blockScopedVariable); // 43 })();
console.log(functionScopedVariable); // ReferenceError: functionScopedVariable is not defined console.log(blockScopedVariable); // ReferenceError: blockScopedVariable is not defined
Inside a block
Variables declared using let
inside a block can't be accessed outside that block.
{ var globalVariable = 42; let blockScopedVariable = 43; console.log(globalVariable); // 42 console.log(blockScopedVariable); // 43 }
console.log(globalVariable); // 42 console.log(blockScopedVariable); // ReferenceError: blockScopedVariable is not defined
Inside a loop
Variables declared with let
in loops can be referenced only inside that loop.
for (var i = 0; i < 3; i++) { var j = i * 2; } console.log(i); // 3 console.log(j); // 4
for (let k = 0; k < 3; k++) { let l = k * 2; } console.log(typeof k); // undefined console.log(typeof l); // undefined // Trying to do console.log(k) or console.log(l) here would throw a ReferenceError.
Loops with closures
If you use let
instead of var
in a loop, with each iteration you get a new variable. That means that you can safely use a closure inside a loop.
// Logs 3 thrice, not what we meant. for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); }
// Logs 0, 1 and 2, as expected. for (let j = 0; j < 3; j++) { setTimeout(() => console.log(j), 0); }
Temporal dead zone
Because of the temporal dead zone, variables declared using let
can't be accessed before they are declared. Attempting to do so throws an error.
console.log(noTDZ); // undefined
var noTDZ = 43;
console.log(hasTDZ); // ReferenceError: hasTDZ is not defined
let hasTDZ = 42;
No re-declaring
You can't declare the same variable multiple times using let
. You also can't declare a variable using let
with the same identifier as another variable which was declared using var
.
var a; var a; // Works fine.
let b; let b; // SyntaxError: Identifier ‘b’ has already been declared
var c; let c; // SyntaxError: Identifier ‘c’ has already been declared
const
const
is quite similar to let
—it's block-scoped and has TDZ. There are, however, two things which are different.
No re-assigning
Variable declared using const
can't be re-assigned.
const a = 42;
a = 43; // TypeError: Assignment to constant variable.
Note that it doesn't mean that the value is immutable. Its properties still can be changed.
const obj = {};
obj.a = 42;
console.log(obj.a); // 42
If you want to have an immutable object, you should use Object.freeze()
.
const obj = Object.freeze({a: 40});
obj.a = 42;
console.log(obj.a); // 40
console.log(obj.b); // undefined
Initializer is required
You always must specify a value when declaring a variable using const
.
const a; // SyntaxError: Missing initializer in const declaration