First, let's quickly examine hoisting. Here is a simple example
sayHello();
function sayHello() {
console.log("hello");
}
This works because function sayHello()
is hoisted. Essentially, it will be pulled at the top of the file, so even if you're calling it "before" it's defined it still works. The JavaScript engine will basically make the effective execution the following:
function sayHello() {
console.log("hello");
}
sayHello();
So, hoisting pertains to moving definitions before anything else runs.
Hoisting also works with variables but only for the definition.
console.log(x, typeof x)
var x = "hello";
In this case, it probably doesn't work as you will initially expect but as I said, the definition is hoisted but not the assignment. Here is the effective code that is running:
var x;
console.log(x, typeof x);
x = "hello";
So, this is the basics of what hoisting is and how it works. There is one more piece of the puzzle missing to fully explain your second snippet - I did use var
on purpose, as it works differently let
and const
declarations. The declaration is hoisted but the variable is not initialised before it reaches the let myVar
line..
I kept it simple until now but there are three steps to making a variable
- declaration, which makes the JavaScript engine aware of the variable
- initialisation which makes the variable usable
- assignment which is giving it a value.
This is why the variable myVar
cannot actually be used before let myVar
initialises it. This will also help demystify the error message:
console.log(myVar);
let myVar = "hello";
The same behaviour exists with const
. This is what a temporal deadzone is - trying to use a let
or const
defined variable before it's "officially" declared.
This will explain why your second snippet does not work. I have put the relevant steps of execution:
printMe(obj); //2. printMe() is called which already exists due to hoisting. The code
// tries to pass in obj which is in a temporal dead zone, which leads to
// ReferenceError citing that reason
function printMe(person) { // 1. the function is hoisted, making it available to be called
console.log(person.firstName, person.lastName);
}
const obj = { // 3. the definition of obj ends the temporal dead zone, so it can be used
//but this line is never reached.
firstName: "John",
lastName: "Wick"
};
As for your first snippet, the things are different - the function printMe()
is only called when the button is pressed and only then is obj
being passed to it, so by the time a user can click on it, obj
will be defined and will be out of a temporal dead zone. If we simplify and remove the attaching of the event handler, the effective execution is the following
function printMe(person) { //hoisted at the top
console.log(person.firstName, person.lastName);
}
// obj exists in a temporal dead zone
const btn = document.getElementById("btn");
btn.onclick = function() { //the click handler is DEFINED but only when
// it's executed will try to resolve either printMe() or obj
printMe(obj);
};
const obj = { //end of the temporal deadzone for obj
firstName: "John",
lastName: "Wick"
};
btn.click(); // a user clicks -> both printMe and obj are available, so there is no ReferenceError
<button id="btn">Click me!</button>