Lawrence Eagles Senior full-stack developer, writer, and instructor.

A guide to classic static blocks in JavaScript

3 min read 872

JavaScript Logo

Introduction

Class static blocks in JavaScript allow you to perform additional static initializations during the evaluation of class definitions. However, class static blocks are currently still included in a stage 2 proposal, which is not intended as a replacement for static fields but is meant to provide new use cases that could not be accomplished using static fields. Therefore, class static blocks make object-oriented programming (OOP) JavaScript much more interesting and powerful.

Programming languages such as Java and C# that use classical inheritance already have implementations like this. In Java, they are static initializers, while in C#, they are static constructors.

Unlike these languages, OOP in JavaScript uses prototypal inheritance. Typically, features like this should not be present. However, with the advent of classes in Ecmascript 2015 (es6), there have been implementations of features that are akin to those seen in classical inheritance. Some, such as static methods and extends, have been implemented already. And now, there are even more experimental features, such as static fields, private fields, and class static blocks.

Despite all of these great evolutions in OOP JavaScript, it is important to note that, under the hood, JavaScript still uses prototypal inheritance. Consequently, a lot of these are mere syntatic sugar.

Syntatic sugar refers to a new, visually appealing syntax (often a shortcut) to perform an old operation. – Wikipedia

Let’s consider the syntax and semantics of class static blocks in JavaScript in the next section.

Syntax and semantics of class static blocks

Syntax

Below is the proposed syntax:

class NewClass {
  static {
     // do something 
  }
}

Semantics

No return statements:

class NewClass {
  static {
    return // syntax error
  }
}

A class definition should have only one static block {} .

class NewClass {
  static {}
  static {} // throws and error.
}

A static block {} creates a new variable environment nested within the scope of the class.

var age = 23
class NewClass {
  static {
      var age; // can still use age inside the static block 
              //because it creates as new lexical scope
      var name = "Lawrence Eagles"
  }
}

console.log(name) // reference error. 
                  // cannot access name inside the static block's lexical scope.

From the code above, we can see that although var age was declared in the same scope as the class, we still create a new age variable inside the class static block. This is because the class static block {} has its own variable environment.

We made a custom demo for .
No really. Click here to check it out.

However, we could not access the var name variable initialized inside the class static block {} outside its local scope.

A static block {} should not have decorators. You are to decorate the class itself, as shown below:

@decorator // ok
class NewClass {
  @decorator // error. not allowed
  static {
  }
}

When evaluated, the this variable of the static block {} points to the constructor function of the class.

You can get more on its semantics here.

Uses cases of static blocks

As previously mentioned, static blocks are not replacements for static fields or static private fields.

They are however, meant to enable more use cases, as seen below:

Evaluating a statement during class initialization:

class NewClass {
  static square = {L: 8, B: 6};
  static y;
  static z;
  // wrong code would throw an error
  try {
      // do something here
    }catch (error) {
      // handle error here
  }
}

The code above would throw an error. We cannot evaluate that try…catch statement during the class initialization. The try…catch statement must be moved outside the class declaration.

However, if we need to evaluate a statement (e.g., try..catch ) inside a class initialization, we can use a static block as shown below:

class NewClass {
  static square = {L: 8, B: 6};
  static y;
  static z;
  static {
    try {
      // do something here
    }catch (error) {
      // handle error here
    }
  }
}

When we need to set two fields from a single value, as seen below:

class NewClass {
  static square = {L: 8, B: 6};
  static y;
  static z;
  NewClass.y = square.L // throws an error
  NewClass.z = square.B // throws an error
}

We can, however, set the values using static blocks as shown below:

class NewClass {
  static square = {L: 8, B: 6};
  static y;
  static z;
  static {
    NewClass.y = square.L // correct
    NewClass.z = square.B // correct
  }
}

When information sharing is required between a class with an instance private field and another class or function declared in the same scope, as shown below:

let getName;
export class NewClass {
  #name
  constructor(devName) {
    this.#name = { data: devName };
  }
  static {
    // getName has privileged access to the private state (#name)
    getName = (obj) => obj.#name;
  }
}

export function getNameData(obj) {
  return getName(obj).data;
}

From the above, we can see that static blocks allow you to evaluate statements in the context of the current class declaration with privileged access to (instance or static) private state.

Although the getName function is evaluated in the class static block {}, it still gets privileged access to the name private state of the class.

You can learn more about the possible uses of class static block {} here.

Conclusion

The development of JavaScript has continuously evolved, especially in OOP JavaScript. Although JavaScript maintains its prototypal inheritance implementation, many new and proposed features are akin to those seen in classical inheritance.

Class static block {} is no different. This development is good for the language, as it now appeals to a broader scope of developers who find the prototypal inheritance a deterrent to adopting JavaScript.

Lastly, class static block {} is a powerful addition to OOP JavaScript but it is still a stage 2 proposal feature.

: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

.
Lawrence Eagles Senior full-stack developer, writer, and instructor.

Leave a Reply