No history yet

JS Core Internals

The JavaScript Engine Room

When you write JavaScript, you're giving instructions to a computer. But how does the computer actually understand and run them? It uses a program called the JavaScript engine. Every time you run a piece of code, the engine creates a special environment to handle it. This environment is called an Execution Context

Execution Context

noun

The environment in which a piece of JavaScript code is evaluated and executed. It contains details about the code's variables, functions, and how to access other parts of your program.

Think of an Execution Context as a private workspace for a specific task. It has two main phases.

  1. Creation Phase: The engine first scans the code. It sets aside memory for all the variables and functions declared in that code. This is a setup step before any instructions are carried out.
  2. Execution Phase: The engine goes through the code line by line and runs the instructions.

When your program starts, the engine creates a Global Execution Context. When a function is called, a new Function Execution Context is created and placed on top of the global one. This stacking mechanism is managed by the Execution Stack, or Call Stack.

It works on a "Last-In, First-Out" (LIFO) principle. The last function to be called is the first one to finish and be removed from the stack. It's like a stack of plates: you add a new plate to the top and you also remove a plate from the top.

Who is `this`?

In JavaScript, this is a special keyword that refers to an object. Which object it refers to depends entirely on how the function containing this is called. This is a common source of confusion, but it follows a clear set of rules.

The value of this is not set when you write the function, but when you run it.

Let's look at the four main rules for how this gets its value.

1. Default Binding If a function is called by itself, without being attached to an object, this defaults to the global object. In a web browser, the global object is window.

function sayHi() {
  console.log(this); // In a browser, this will log the 'window' object
}

sayHi(); // Just a plain function call

2. Implicit Binding When you call a function as a method of an object, this refers to the object the method is called on. The object is to the left of the dot.

const person = {
  name: 'Alice',
  greet: function() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

person.greet(); // 'this' refers to 'person'. Output: "Hello, I'm Alice"

3. Explicit Binding You can explicitly tell a function what this should be by using the .call() or .apply() methods. The first argument you pass to these methods becomes the value of this inside the function.

function introduce() {
  console.log(`My name is ${this.name}`);
}

const student = { name: 'Bob' };
const teacher = { name: 'Charlie' };

introduce.call(student); // Output: "My name is Bob"
introduce.call(teacher); // Output: "My name is Charlie"

4. new Binding When you use the new keyword to create an instance of an object from a constructor function, this inside that function refers to the brand new object being created.

function Car(make) {
  // A new empty object is created, and 'this' is set to it.
  this.make = make;
}

const myCar = new Car('Honda');
console.log(myCar.make); // Output: "Honda"

The Arrow Function Exception

Arrow functions, introduced in ES6, behave differently. They don't have their own this binding. Instead, they inherit this from their parent scope at the time they are defined. This behavior is called lexical scoping

const user = {
  name: 'Dana',
  // Regular function: 'this' is the 'user' object
  sayNameRegular: function() {
    console.log(this.name);
  },
  // Arrow function: 'this' is inherited from the global scope (e.g., window)
  sayNameArrow: () => {
    console.log(this.name); 
  }
};

user.sayNameRegular(); // Output: "Dana"
user.sayNameArrow();   // Output: undefined (because window.name is not set)

Key takeaway: Use regular functions for object methods. Use arrow functions when you want this to mean the same thing it does in the surrounding code.

Declarations Before Execution

Remember the Creation Phase of the Execution Context? This is where JavaScript sets up variables and functions. This process is known as hoisting The engine "hoists" or lifts declarations to the top of their scope before the code is executed.

However, var, let, and const are hoisted differently.

var Hoisting Declarations using var are hoisted and initialized with undefined. This means you can access them before the line where they are declared without getting an error, but their value will be undefined.

console.log(myVar); // Output: undefined
var myVar = 10;
console.log(myVar); // Output: 10

let and const Hoisting Declarations with let and const are also hoisted, but they are not initialized. They are in a state called the Temporal Dead Zone (TDZ) from the start of their scope until the line where they are declared. Trying to access them in the TDZ results in a ReferenceError.

// console.log(myLet); // Throws ReferenceError: Cannot access 'myLet' before initialization
let myLet = 20;
console.log(myLet); // Output: 20

The Temporal Dead Zone helps prevent bugs by ensuring you can't use a variable before it has been declared and assigned a value.

Function declarations are hoisted completely. This means you can call a function before you've written it in the code.

hello(); // Output: "Hello, world!"

function hello() {
  console.log("Hello, world!");
}

Understanding these internal mechanics is key. It helps you predict how your code will behave, debug issues faster, and write more reliable JavaScript.

Now, let's test your understanding of these core concepts.

Quiz Questions 1/6

What are the two main phases of a JavaScript Execution Context?

Quiz Questions 2/6

The Execution Stack (or Call Stack) in JavaScript manages Execution Contexts based on which principle?

Mastering these concepts separates beginners from experienced developers. They form the foundation for understanding more complex topics in JavaScript.