DEV Community

Cover image for ๐Ÿš€How JavaScript Works (Part 3)? Function Scope, Block Scope
Sam Abaasi
Sam Abaasi

Posted on

๐Ÿš€How JavaScript Works (Part 3)? Function Scope, Block Scope

In the world of JavaScript and programming, understanding scopes is crucial. Scopes, organized by functions, are the building blocks of your code's structure. But what can we do with this knowledge of scopes? Why is it important, and how can it help us solve real-world coding challenges? Let's dive into these questions.

Table of Contents

Naming Collision Problem

One common challenge in programming is dealing with naming collisions. This occurs when two or more identifiers share the same name within the same scope, potentially leading to ambiguity and conflicts. To mitigate this issue, we can leverage our understanding of scopes.

To illustrate the significance of scopes, consider a simple scenario. You have a variable, and later in your code, you use that variable, expecting it to still hold the same value. Everything seems fine until another developer, well-intentioned but unaware of the variable's usage, inserts some code in between. The result? A naming collision.

var teacher = "Alice";

// ... (some code)

var teacher = "Bob";

Enter fullscreen mode Exit fullscreen mode

In this case, a variable named teacher was unintentionally redeclared in the same scope, causing unexpected behaviour. The problem is not that the variable could be reassigned; it's the naming collision that's the root issue.

The problem with using const in this specific context is that const prevents you from reassigning the variable, but it does not prevent you from declaring a new variable with the same name in the same scope. In other words, const enforces that the variable assigned is not reassigned within its scope, but it doesn't guard against naming collisions within the same scope.Consider the following example:

const teacher = "Alice";

// ... (some code)

const teacher = "Bob"; // Syntax error due to redeclaration

Enter fullscreen mode Exit fullscreen mode

This code will throw a syntax error because you're attempting to redeclare a variable that has been declared with const. In this context, the use of const is causing a problem because you're not allowed to redeclare teacher.

The Principle of Least Exposure

To solve such problems, we turn to the "Principle of Least Exposure" or "Principle of Least Privilege." This principle suggests defaulting to keeping everything private and only exposing what's absolutely necessary. Why is this principle so essential in software engineering?

Preventing Naming Collisions

By reducing the surface area for name collisions, we avoid conflicts that arise when two different entities within the same scope try to use the same semantic name for different purposes. Hiding variables within a scope minimizes the chances of naming collisions.

Preventing Misuse

When something is hidden within a scope or kept private, it prevents accidental or intentional misuse. Exposing variables publicly often leads to unintended use by other developers, even if they're advised not to use them. Hiding variables restricts their access.

Enabling Future Refactoring

Perhaps the most crucial benefit of the principle is its ability to protect against future refactoring issues. When something is exposed publicly, it's almost guaranteed that someone will use it. Consequently, when you want to refactor or change the implementation, you risk breaking the existing code that depends on it. Hiding variables provides the freedom to refactor without worrying about breaking dependent code.

The Function Approach

One way to apply the principle of least exposure is by creating functions to encapsulate variables and prevent naming collisions. For instance:

function anotherTeacherScope() {
    var teacher = "Bob";
    // ...
}
Enter fullscreen mode Exit fullscreen mode

This approach introduces a new scope, reducing the likelihood of naming collisions. However, it comes with a trade-off. While it resolves the collision problem, it introduces a function with a name in that scope, potentially shifting the naming collision issue rather than solving it.

IIFE Pattern

To address this problem more effectively, we need a way to create a scope without introducing a new variable name. This approach should allow us to prevent naming collisions without shifting the problem.

One potential solution involves understanding how the code execution process works. By breaking down code execution into two steps - retrieving a variable's value and executing it - we can create a scope that doesn't pollute the enclosing scope with additional variable names. Consider the following example:

(function () {
    var teacher = "Bob";
    // ...
})();
Enter fullscreen mode Exit fullscreen mode

In this code, we've wrapped our code in a function expression and immediately invoked it. While it may look unconventional, it achieves the desired result. It creates a new scope, isolates the variable, and prevents naming collisions without introducing a new variable name.

Immediately Invoked Function Expressions (IIFE)

Immediately Invoked Function Expressions (IIFE). An IIFE is an anonymous function expression that is defined and executed immediately. It serves as a temporary scope, providing structure and isolation for your code. IIFEs can be named or anonymous.

The key advantage of IIFEs is that they allow you to create a scope without polluting the surrounding scope with names. They are often used to handle naming collision issues and provide clarity to your code.

One common use case is to encapsulate code that should only run once, such as setting up configurations. IIFEs create a temporary context for this code to execute and then disappear.

(function() {
    // Your code here
})();
Enter fullscreen mode Exit fullscreen mode
  • Passing Values to IIFEs: IIFEs are regular functions, which means you can pass arguments to them. This feature adds flexibility, enabling you to pass values and parameters to your IIFE's scope.

Avoid Anonymous IIFEs: While you can use anonymous IIFEs, it's advisable to give them a name that reflects their purpose. Using meaningful names for your IIFEs improves code readability and debugging. Avoid anonymous function expressions, including IIFEs.

A Better Solution: Block Scoping

When it comes to managing variable scope and preventing naming collisions, JavaScript provides a more robust solution known as block scoping. Unlike traditional variable declarations with var, block scoping using let and const allows developers to create isolated scopes within their code. This approach brings a higher level of precision to the control of variables and is particularly useful in scenarios where you want to confine the scope of variables to specific blocks of code.

Understanding Block Scoping

Block scoping leverages curly braces {} to create discrete, limited scopes for your variables. With let and const, you can declare variables within a block, ensuring they are only accessible and relevant within that specific block.

Consider the following example:

var teacher = "Alice";
{
  let teacher = "Bob";
  console.log(teacher); // Bob
}
console.log(teacher); // Alice
Enter fullscreen mode Exit fullscreen mode

In this example, two separate teacher variables existโ€”one within the block and one outside of it. The inner teacher variable, declared with let, is confined to the block, ensuring it doesn't interfere with the outer variable.

Benefits of Block Scoping

Block scoping provides a range of benefits:

  • Granular Control: With block scoping, you can finely control where your variables are accessible. This is invaluable in scenarios where you want to avoid accidental variable interference.

  • Preventing Naming Collisions: Block-scoped variables eliminate the risk of naming collisions within a function or code section. Each block maintains its own scope, ensuring variables don't clash.

  • Improved Code Clarity: By using block scoping, you explicitly define where a variable is meant to be used, enhancing the readability and maintainability of your code.

  • Reduced Bugs: Naming conflicts and unintentional variable reassignments can lead to bugs and unexpected behavior. Block scoping helps you catch such issues at the development stage.

Function Declaration vs Function Expression

JavaScript provides two primary mechanisms for creating functions: function declarations and function expressions. The choice between these two approaches significantly impacts your code's organization and visibility.

  • Function Declaration:
    A function declaration is defined with the function keyword as the first thing in a statement. It adds the function's name to the enclosing scope, making it accessible from anywhere within that scope.

  • Function Expression:
    A function expression is a more versatile construct. It arises when the function keyword is not the first thing in a statement. Instead, it's often associated with variables, operators, or parentheses. If it lacks a name, it becomes an anonymous function expression.

The distinction between these two approaches is pivotal, influencing the scope and accessibility of your functions, ultimately affecting your project's organization and functionality.

function teacher() {}
var myTeacher = function anotherTeacher() {
    console.log(anotherTeacher)
}
console.log(teacher)
// function teacher() {...}
console.log(myTeacher)
// function anotherTeacher() {...}
console.log(anotherTeacher)
// ReferenceError: anotherTeacher is not defined.
Enter fullscreen mode Exit fullscreen mode

function expressions are read-only: you cannot assign it to other value.

var clickHnadler = function () {}
// Anonymous Function Expression
var keyHandler = function myKeyHandler() {
    //โ€ฆ
}
// Named Function Expression
Enter fullscreen mode Exit fullscreen mode

Named Function Expression vs Anonymous Function Expression

The decision to use a named or anonymous function expression depends on your project's requirements and coding style. Consider the benefits of each:

  • Named Function Expression

    • Reliable Self-Reference: Named function expressions provide a reliable self-reference, making them suitable for recursion or self-referential use within the function.
    • Event Handling: They are useful for event handlers that may need to unbind themselves.
    • Accessing Properties: If you need to access properties on the function object, such as its name, named function expressions are more appropriate.
  • Anonymous Function Expression

    • Conciseness: Anonymous function expressions offer brevity, which can be beneficial for small, simple functions.
    • Readability: However, they may sacrifice readability, as developers need to infer the function's purpose solely from its body and context.

To tie these concepts back to scopes, consider how named and anonymous function expressions affect scope management. By choosing one over the other, you can control the visibility and accessibility of your functions, thereby influencing your project's scope organization.

Conclusion:

In the world of JavaScript, mastering scopes is akin to wielding a powerful tool for organized and unambiguous coding. Function declarations, function expressions, and IIFEs provide us with the means to structure our code effectively. The Principle of Least Exposure further reinforces the importance of encapsulating variables and preventing naming collisions. Always opt for named function expressions over anonymous ones, and remember that code should be written not only for machines but also for human developers. While arrow functions offer brevity, they can sometimes sacrifice readability, making it vital to choose the right tool for the job. By applying these concepts, you'll write cleaner, more maintainable code, and make your programming journey smoother.

Sources

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

Top comments (0)