Are you already using let
? If you have just started to use it, you could have a question — “should I still use var
somewhere?”
Let’s first catch the differences between var
and let
.
Scope
As we know, var
is function scoped, let
is block scoped.
Function scope — var
is not visible outside, neither let
would be:
function testScope() {
var variable = 'text of var'
console.log(variable) // "text of var"
}
testScope()
console.log(variable) // ReferenceError: variable is not defined
Block scope — var
is visible in the closest ancestor function scope, but let
is not visible outside of the block
if (true) {
var variable = 'text of var'
let letiable = 'text of let'
console.log(variable) // "text of var"
console.log(letiable) // "text of let"
}
console.log(variable) // "text of var"
console.log(letiable ) // ReferenceError: letiable is not defined
Why is let
better? It’s more declarative and less error prone.
If you declare a var
inside an if
block, it could still be used outside of it. There’s no warranty that the code below will not rely on it. Yes, your intention could be to not use it outside, but still leaving the possibility to do this leads to unexpected bugs.
Immagine that you are reading someone else code
if (flag) {
var msg = "Be aware of hoisting and scope rules";
/* some other code */
}
How possibly can you know where else that msg
variable could or even should be used? With let
you are sure that it cannot be used outside of the if
block.
There’s an interesting bonus for using let
inside for
loops.
Let’s consider this example first:
var i = 0
while (i < 5) {
let index = i
setTimeout(function () {
console.log(index) // will show 0, 1, 2, 3, 4
})
i++
}
It will not work with var index
, the print would be “4, 4, 4 ,4, 4”.
With a for
loop, you don’t need any intermediary variable.
for (let i=0; i<5; i++) {
setTimeout(function () {
console.log(i) // will show 0, 1, 2, 3, 4
})
}
Attention, please. It works because inside the for
loop the let
variable in the initialization expression is redeclared at each iteration with the resulting value of the previous iteration. It’s an ES6 feature made for us to manage easier for
loops.
// explaining the first iteration
for (let i=0; i<5; i++) {
// the lexical variable `i` equals to `0`
setTimeout(function () {
console.log(i) // will show 0, 1, 2, 3, 4
})
// after the expression statement is executed
// the final expression `i++` is evaluated and `i` value becomes `1`
// `let i` is redeclared assigning it the result of the final expression `let i = 1`
}
As you expect, let
is not visible outside the loop, but it can be redeclared inside it. That’s because the initialization expression is in an outer block for the statement expression.
for (let i=0; i<5; i++) {
let i = 'same value'
setTimeout(function () {
console.log(i) // will show "same value", "same value", "same value", "same value", "same value"
})
}
Global scope
Unlike var
, the let
are not attached to the window
object.
var variable = 'var text'
let letiable = 'let text'
console.log(
window.variable, // "var text"
window.letiable // undefined
)
You could say, well, I can use var
to save properties on window
object!
That’s not a good practice! Be explicit — window.UserModule = {}
. Sure, remember that we all should avoid using window
object for that puproses, because it’s accessible from everywhere! Name collisions are guaranteed! Use ES6 modules instead.
Hoisting
Beside the declaration, which is hoisted for all the declarations in ES6, a var
is initialized with undefined
, but let
is not initialized.
variable = 'This is a hoisted and initialized variable.'
console.log(variable) // "This is a hoisted and initialized variable."
/** a lot of code */
var variable;
The var
is initialized. That’s why you can access it.
Look at this example:
var variable = 'This is outer variable.'
function hoistingExample () {
variable = 'This is inner variable.' // seems like we are changing our outer variable value, right?
console.log(variable) // "'This is inner variable."
/** a lot of code */
var variable;
}
hoistingExample()
console.log(variable) // "'This is outer variable."
As var
has function scope, you will misunderstand the code. The first thing you’ll figure out is that variable
in outer scope will be reassigned when you invoke the function, but it will not.
A let
variable is uninitialized before the let
statement. This leads us to a better and readable code.
letiable = 'This variable cannot be accessed.' // ReferenceError: letiable is not defined
let letiable;
Isn’t it great?
Temporal Dead Zone
The hoisting explaining above lead us here. The Walking Dead Zone! It’s the zone inside a code block before a let
statement. You cannot access a lexical variable let
before its statement is evaluated!
Here is an important example to see:
let str = 'outer text'
if (true) {
let str= str // ReferenceError: str is not defined
}
The second, inner let str
is hoisted. This means that it becomes the variable which is referenced inside the if
scope when you try to access it. That means that we cannot more access the outer let str
. The outer str
is cut out for us, so let’s discuss the inner let str
.
The inner str
it’s not initialized yet. So, when you write let str = str
you are trying to assign it itself. Remember that the assignment is evaluated right-to-left. That’s why we get the error here. The str
on the right is evaluated first, and it’s not initialized yet. That’s it!
Here another example with a for...of
to be aware of:
let a = ['lexical', 'variable', 'example']
for (let a of a) { // ReferenceError: a is not defined
/** some code */
}
Declaration
A var
can be redeclared, a let
cannot!
var variable = 'var text'
var variable = 'changed var text'
console.log(variable) // "changed var text"
let letiable = 'let text'
let letiable = 'changed let text' // SyntaxError: Identifier 'letiable' has already been declared
As in previous examples, more declarative, less error prone!
Style
Around the web you can find advices to keep using var
for style purpose — declaring them for function scope or even global scope, keep the let
for block scopes.
Well, having “lets” and still using “vars” it’s like shooting yourself in the foot! Using var
means keeping dangers to buggy behaviour mainly due to its hoisting and scope implementations.
Actually, using let
force us to write a better code both stylistically and less error-prone. It forces us to write more declarative, easier to understand and more safe code.
A great thing — is simplicity. Simplicity to understand and to use. Let’s safe us from all those issues and misunderstandings. We should write instructions that make things alive, and not losing time for solving bugs that make them dead!
Conclusion
If you are still in doubt, or feel some inner opposing to shift completly to using let
— you should know that you’re not alone. I bet that even best and experienced programmers feel that (or did it).
We oppose to new things! It’s natural to us. But we adapt really fast, so a couple of scripts with let
— and you will feel like you was using it the whole life!
Wait a moment… And the final answer? Do we still need vars? Sure! But, I’d rather say that vars need us. In the legacy code, of course. Beside that, I don’t see a single reason you should use vars in your new modern ES6+ code. If you know any, please let us know, — below in the comments, in a blog post, or even in a youtube advertising — anything!
P.S.: Yeah, I know, there could be a discussion about using const
almost everywhere and keep using let
as low as possible, but that’s another topic! Stay tuned!