Advanced JavaScript ES6 — Temporal Dead Zone, Default Parameters And Let vs Var — Deep dive!

Lidor Avitan
Nielsen-TLV-Tech-Blog
6 min readAug 15, 2019

Intro 🍕

Well, before we start deep diving into Temporal Dead Zone, which is a super interesting topic, We have to first understand the core concept of let vs var.
As you all know, let and const were born somewhere in ECMAScript 2015 (6th Edition, ECMA-262) and while we’re going to talk about let vs var, it’s definitely the same differences for const as well.

TL;DR

Can you guess what will be the output? and why?

let x = 2;
function foo(y = x , x) {
x = 3;
console.log(x)
}
foo();

Topics

  • let vs var, General Differences.
  • The relation between let And ES6 Default Parameters.
  • Temporal Dead Zone — No, it isn’t a scary movie! 👻🎃

Let vs Var General Differences

1 Function Scope and Block Scope is the first and most known difference. Well, Variables which are declared with var are scoped only by the function block scope, while let variables are scoped by the nearest block scope. 🚨 Hold on! of course, I will give you some examples for that crazy explanation.

//example with Function Scope
var myVar = 1;
function someScopeFunc() {
var myVar = 10;
console.log(myVar) // log 10
}
someScopeFunc()
console.log(myVar) // log 1

Obviously, we can see myVar in the global environment didn’t change, even if we declared and assigned a variable with the same name inside the someScopeFunc scope. myVar which is declared in someScopeFunc is known as a local variable of his wrapper function environment and that’s why it doesn’t affect the global scope. In this case, it will work the same for let and const. Ok, now let’s see the diff with Block scope

//example with Block scope//var
var myVar = 2;
if(true) {
var myVar = 10;
}
console.log(myVar); // 10
//let
let someVar = 2;
if(true) {
let someVar = 10;
}
console.log(someVar); // 2

All right, now we can see the differences for Block Scope. when I said Block scope, I meant every line of code that is wrapped by curly braces. Definitely, it could be a for-loop, switch-case, if-statement, etc.. Anyway, as you can see in the first example of myVar it affects the global scope, which means variables declared with var don’t really care about block scope. On the other hand, someVar was declared with let so the second declaration (inside the if-statement) doesn’t affect the global, that means someVar is scoped by the nearest block scope.

2 Global Object, it’s a small difference but super important. Variables that are declared by var are automatically attached to the global object which is called window in the browser environment. With let it’s not the case, let doesn’t affect the global object.

let me = 'go'; // not globally scoped
var you = 'able'; // globally scoped
console.log(window.me); // undefined
console.log(window.you); // 'able'

3 let and const are not assigned with undefined in the creation phase, they exist but without any value and you can’t access them until they are assigned.😱 Interesting? for sure, but I will explain that in more detail in the Temporal Dead Zone section.

4Redeclaration, It’s simple. when you declare variables by var, “…declare as much as you can…”. not really :), but the engine doesn’t really care. An example will be better.

let me = 'foo';
let me = 'bar'; // SyntaxError: Identifier 'me' has already been declared
var you = 'foo';
var you = 'bar'; // No problem, `you` is replaced.

Note: for const, you can’t redeclare and also can’t re-assign.

Default values of parameters

defaults parameters is a nice topic by itself, but there is a kind of relation to Temporal Dead Zone. it came with ES6 and as you’re gonna see some of the rules above will certainly be in affect here as well.

Actually, we all know that function parameters in ES5 are part of the local variable environment of the execution of any given function. In ES6 it’s a bit different. For the function parameters, there is another layer in the execution that is called intermediate scope, which is created specifically to store parameters bindings. Why does it happen? The variables in the body with the same name should not affect variables captured in closures bindings with the same name. Whaaat??!😤, I know! code example.

var myVar = 1;
function foo(myParam = function() { return myVar + 3 } ) {
var myVar = 5;
console.log(myParam()) // log 4 :)
}
foo()

Well, when we invoke myParam function, like every execution in JavaScript, the engine will look for myVar inside myParam function scope(variables environment), then goes down in the scope chain. but because of the intermediate scope, the engine won’t look inside foo scope, it goes down to the intermediate scope, in our case he can’t find it so it goes next to the global scope.

let’s look at another example:

let x = 2;
function b(y = x , x) {
x = 3;
}
b(); // Cannot access 'x' before initialization

Ok, now it’s more reasonable. As we said above about let/const that you can’t access the variable before you assigned his value. for default parameters, it’s the same case. which means, the engine creates special scope(intermediate scope) to the function parameters and because you try to assign x to y before you assign a value to x variable. it’s failing! but why does it happen?…(drum roll…)…The Star of our show: Temporal Dead Zone ⭐️

Temporal Dead Zone

Well, who are you TDZ? To understand how it actually works, we need to know how the execution context works. the JavaScript engine runs through the code twice, in two phases types, Creation Phase and Execution Phase. No, I’m not kidding :). In general, that’s what sometimes happens when we say “Hoisting”:

console.log(myVar) // undefiend
var myVar = 2;
console.log(myVar) // 2

A lot of people would tell you that “Hoisting” meaning variables physically moved to the top, Well, I’ve seen this nice sentence in MDN that explains that this is not really what is happening.

Conceptually, for example, a strict definition of hoisting suggests that variable and function declarations are physically moved to the top of your code, but this is not in fact what happens. Instead, the variable and function declarations are put into memory during the compile phase, but stay exactly where you typed them in your code. [Hositing]

And it’s true. actually, the engine goes first through the code in Creation Phase and allocate memory space for variables that are declared with var, there is another player here that is called the initializer, which is responsible to initialize their value to undefined. In the second phase(Execution Phase), the engine goes through the code and executes. So, at the first line, console.log(myVar) should log undefined, because the variable exists in the memory with a special value undefined from the first phase. At line two, the engine assigns the value of myVar variable from undefined to 2, and line three should log the value 2.

Finally, For most of the ES6 features (let, const, default parameters, etc), The Creation Phase work quite differently. It still goes through the code and allocates space for variables, but the initializer set the variables with a special mode called TDZ (Temporal Dead Zone), which means the variables exist but you can’t access them until you assign some value.

let x = 'outer scope';
(function() {
console.log(x); // ReferenceError
let x = 'inner scope';
}());

Yeah, We're Done! 🚀

let’s go back to the example we saw before:

let x = 2;
function b(y = x , x) {
x = 3;
}
b(); // Cannot access 'x' before initialization

Do you remember? 😎 So, because function parameters (y = x , x) have their own scope, and ES6 uses TDZ, it’s simple to answer why we can’t access the variable x before we assign some value.

Please post any feedback, questions, or requests for topics. I would also appreciate 👏 if you like the post, so others can find this too.

you can follow me on GithubStackoverflowTwitter

Thanks, and see you next time!

Credit: I was inspired by Dmitry Soshnikov, who has an amazing advanced JavaScript blog. thanks!.

--

--

Lidor Avitan
Nielsen-TLV-Tech-Blog

FrontEnd Engineer | enthusiastic JavaScript core researcher