DEV Community

Cover image for ๐Ÿš€How JavaScript Works (Part 7)? `this`
Sam Abaasi
Sam Abaasi

Posted on

๐Ÿš€How JavaScript Works (Part 7)? `this`

The this keyword in JavaScript has long been a source of confusion and frustration for many developers. It often behaves in ways that seem unintuitive, leading to a sense of bewilderment. However, this is an incredibly powerful mechanism that allows for dynamic context switching in your code. To master it, we need to understand how it works in JavaScript, free from preconceived notions based on other programming languages.

Table of Contents

Lexical Scope vs. Dynamic Scope
The Pitfall of Comparisons
The Basics: this Represents the Execution Context
Flexibility and Reusability through this
Explicitly Setting the Context
The Four Ways to Invoke Functions
Conceptualizing Different Buildings
Conclusion
Sources

Lexical Scope vs. Dynamic Scope

Before diving into function invocation, it's crucial to understand two fundamental concepts related to JavaScript's scoping: lexical scope and dynamic scope.

Defining Lexical Scope:

Lexical scope, also known as static scope, refers to the idea that scopes are nested within each other. In this model, a compiler, parser, or processor determines all scopes during the compilation phase, well before the code is executed. This means that the structure of scopes and variable access is decided at author time, during the development of the code.

  • Compile-Time Decisions:
    Lexical scope is closely related to compile time. When we talk about lexical scope, it means that decisions about variable access, function nesting, and scope hierarchy are made at compile time. These decisions are not influenced by what happens at runtime during program execution.

  • Fixed and Predictable Scope:
    The variable references in lexical scope are determined during compilation and do not change during runtime. This means that, at compile time, it's known which variables belong to which scopes.

  • Optimizability:
    Lexical scope is highly optimizable because variable access is determined at compile time. There's no need for the runtime environment to continually look up variable references. Even in cases where variables may not be known at compile time, they can be resolved during the initial execution and remain consistent thereafter.

It's important to note that, despite some nuances, JavaScript fundamentally operates with lexical scope.

Dynamic Scope

Dynamic scope, in contrast, allows runtime conditions to affect scope and variable access. While this model is rare in JavaScript, it's more common in languages like Bash scripting. Dynamic scope offers flexibility, but it can be challenging for developers accustomed to lexical scope.

  • Flexibility of Dynamic Scope: Dynamic scope allows a function to have different variable resolutions when called from different places. This flexibility can be seen as both powerful and potentially chaotic. While it may seem unfamiliar to developers accustomed to lexical scope, it offers the potential for extensive code reuse.

In JavaScript, you can achieve a level of flexibility similar to dynamic scope using various this binding techniques, as we'll see shortly.

They are simply different models for scoping. Lexical scope is fixed and predictable, determined at author time during compilation, while dynamic scope is runtime-dependent and flexible.

The Pitfall of Comparisons

One common mistake is trying to relate JavaScript's this to the behavior of this in other languages. This can lead to confusion and hinder your ability to understand and utilize JavaScript effectively. It's essential to approach this as a unique JavaScript feature.

The Basics: this Represents the Execution Context

The this keyword in JavaScript represents the execution context for a function. Unlike many other languages where this refers to the context of the function's definition, in JavaScript, it's all about how the function is called. It's not determined by where a function is defined but by how it's called This means that this is dynamic and is determined by the context of the function call.

Here's a simple example to illustrate this point:

function sayHello() {
    console.log(`Hello, ${this.name}!`);
}

const person1 = { name: "Alice", sayHello };
const person2 = { name: "Bob", sayHello };

person1.sayHello(); // Outputs: Hello, Alice!
person2.sayHello(); // Outputs: Hello, Bob!
Enter fullscreen mode Exit fullscreen mode

In this example, the sayHello function's this context dynamically changes based on the object from which it is called.

Flexibility and Reusability through this

JavaScript's this behavior provides a level of flexibility and reusability in your code that might seem counterintuitive at first. By changing the context of a function when invoking it, you can reuse the same function with different objects or settings.

For instance:

const teacher1 = { name: "Alice" };
const teacher2 = { name: "Bob" };

function introduce() {
    console.log(`I am ${this.name}, your teacher.`);
}

introduce.call(teacher1); // Outputs: I am Alice, your teacher.
introduce.call(teacher2); // Outputs: I am Bob, your teacher.
Enter fullscreen mode Exit fullscreen mode

The call method allows you to alter the context in which the introduce function operates. This dynamic flexibility can be incredibly powerful in real-world JavaScript applications.

Explicitly Setting the Context

In JavaScript, you can explicitly set the context in which a function operates using methods like call, apply, and bind. Each of these methods allows you to change the this context for a given function.

Consider these examples:

const person = { name: "Alice" };

function greet(greeting) {
    console.log(`${greeting}, ${this.name}!`);
}

greet.call(person, "Hello"); // Outputs: Hello, Alice!
greet.apply(person, ["Hi"]);  // Outputs: Hi, Alice!

const greetToAlice = greet.bind(person);
greetToAlice("Hey");  // Outputs: Hey, Alice!
Enter fullscreen mode Exit fullscreen mode

call and apply are used for immediate function invocation, and you can pass arguments directly.
bind creates a new function with the specified context, which can be invoked later.

The Four Ways to Invoke Functions

In JavaScript, there are four primary ways to invoke functions, each influencing how the this keyword behaves:

  • Function Invocation: Invoking a function as someFunction() uses the global context.
  • Method Invocation: Invoking a method as object.someMethod() sets this to the object.
  • Constructor Invocation: Using new SomeConstructor() assigns this to the new instance created by the constructor.
  • Indirect Invocation: Employing someFunction.call(context, args) specifies the context explicitly.

Conceptualizing Different Buildings

A useful metaphor for understanding this in JavaScript is to think of it as deciding which building to enter. Just as you'd ask which building contains the desired office, you must identify the specific context to determine how this will behave.

conclusion

this in JavaScript is a dynamic and powerful concept, designed to enable flexibility and reusability in your code. While it can be a source of confusion, a deeper understanding of how it works and some practical experience with different function invocations will empower you to harness its capabilities effectively. Shedding preconceived notions about this from other programming languages is the first step towards mastering it in the world of JavaScript.

Sources

Kyle Simpson's "You Don't Know JS"
MDN Web Docs - The Mozilla Developer Network (MDN)

Top comments (0)