1

why does this code work

const btn = document.getElementById("btn");

btn.onclick = function() {
  printMe(obj);
};

function printMe(person) {
  console.log(person.firstName, person.lastName);
}

const obj = {
  firstName: "John",
  lastName: "Wick"
};
<button id="btn">Click me!</button>

and/but this does not (temporal dead zone I know)

printMe(obj);

function printMe(person) {
  console.log(person.firstName, person.lastName);
}

const obj = {
  firstName: "John",
  lastName: "Wick"
};

does it have something to do with hoisting? please recommend further reading on this topic.

d_oram
  • 87
  • 1
  • 9
  • 3
    Well, it's very tangentially related to hoisting. Basically, the problem is that you're calling `printMe` before `obj` exists while in the first example, you will only call the function when the button is clicked, which will be after `obj` is created. Hoisting only accounts for `printMe` existing even though you define the function after you call it in the second snippet. – VLAZ May 13 '18 at 17:12
  • "which will be after obj is created" how so? – d_oram May 13 '18 at 17:19
  • 2
    You attach the click handler, then create the object. It's not possible for the user to click the button before both of these are done, so by the time they do, the object is created, so there will be no issue. I'm drafting a better explanation as answer. – VLAZ May 13 '18 at 17:22
  • 1
    @d_oram - _how so?_ - because it is unlikely you can click that button prior to the script running to completion. Therefore, the script will complete, putting the object in memory. Then, eventually, the button will get pushed. – Randy Casburn May 13 '18 at 17:23
  • 2
    Replace `printMe(obj);` with `setTimeout(() => printMe(obj));`. This works because of the same reason the first example works: You are delaying the call to `printMe` untill after the object `obj` get defined. – ibrahim mahrir May 13 '18 at 17:29
  • @RandyCasburn Even if the user manages to click the button before `const obj = {...` is reached, the click won't be processed untill after the assignment is done. Javascript can only do one thing at a time. – ibrahim mahrir May 13 '18 at 17:40
  • @ibrahimmahrir - yep, makes sense, the stack will empty before the event loop puts the handler onto the stack. – Randy Casburn May 13 '18 at 17:46
  • Why on Earth would you write code like this? Your question is "why does this awful thing you should never do work in this one case but not the other"? – Jared Smith May 13 '18 at 18:09
  • @JaredSmith I think the intention is to provide a minimal, complete, and verifiable example for a given behaviour, not really "this is how I write code". I can't think of a single time I've ever written a "hello world" program as shown in tutorials, yet they seem to use that without it being an issue. – VLAZ May 13 '18 at 18:19

2 Answers2

4

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

  1. declaration, which makes the JavaScript engine aware of the variable
  2. initialisation which makes the variable usable
  3. 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>
VLAZ
  • 26,331
  • 9
  • 49
  • 67
  • [`let` and `const` are "hoisted" as well](https://stackoverflow.com/q/31219420/1048572) - it's the reason that "*the JavaScript engine knows myVar exist*" as you say. – Bergi May 13 '18 at 18:18
  • I actually had a sentence to that effect in my initial version but then revised it to avoid some confusion. I'll add it back it, if you think it's a good idea. – VLAZ May 13 '18 at 18:22
  • Just say "Those two are not *initialised* before their definition" (instead of "they are not hoisted"). Btw I'd also use the term "declaration" for what is hoisted. – Bergi May 13 '18 at 18:23
  • yeah, the last example it is! – d_oram May 13 '18 at 18:25
  • @Bergi damn, I now realise why you objected. Somehow while editing `let and const are hoisted but differently` and going to `let and const are not hoisted the same way` I ended up with the sentence `let and const are not hoisted`. Oops! Anyway, I decided to add a bit more on the lifecycle of variables, as it it helps explain the error message which is otherwise a bit cryptic. – VLAZ May 13 '18 at 18:38
1

Declarations are hoisted, assignments are not. Thus in second code, function is hoisted and defined while invocation to printMe(obj) calls on declared but undefined OR rather unintialized reference obj thus would result in ReferenceError

In first example you are not calling a function until a later point (on button click) during which everything is declared and defined as expected.

More reading here: https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch4.md#chicken-or-the-egg

Rikin
  • 5,351
  • 2
  • 15
  • 22