DEV Community

Cover image for Class Static Initialization Blocks in JavaScript
Alex Devero
Alex Devero

Posted on • Originally published at blog.alexdevero.com

Class Static Initialization Blocks in JavaScript

Class static initialization blocks are one feature that will be part of the ECMAScript 2022 proposal. This is one of those features you may not use as often. Yet, it can still be useful from time to time. This tutorial will help you learn about what class static initialization blocks are and how to use them.

Classes, fields and field initialization in a brief

When you create a JavaScript class, you can add public, static and private fields and methods. Which type of a property or method you choose will depend on current situation and your intent. Public fields and methods are better for some situation while private are better for another. You can initialize these fields or not.

You can also define and initialize class properties inside the constructor method. This is becomes especially useful when you want to create class properties based on class parameters. The constructor method allows you to initialize also public as well as private fields.

// Create a class:
class Person {
  // Define public field:
  isAlive = true
  numberOfLegs = 2
  numberOfArms = 2

  // Define private field:
  #iq

  // Define custom properties in constructor:
  constructor(name, age, iq) {
    this.name = name
    this.age = age

    // Initialize private field "iq":
    this.#iq = iq
  }

  // Add private method:
  #decreaseIq() {
    this.#iq--
  }

  // Add public methods:
  sayHello() {
    return `Hello, my name is ${this.name}.`
  }

  watchSitcom() {
    // Call private method "decreaseIq()":
    return this.#decreaseIq()
  }

  tellYourIq() {
    // Return value of private field "iq":
    return this.#iq
  }
}

// Create instance of Person class:
const josh = new Person('Josh', 31, 125)

// Log "josh":
console.log(josh)
// Output:
// Person {
//   isAlive: true,
//   numberOfLegs: 2,
//   numberOfArms: 2,
//   name: 'Josh',
//   age: 31,
//   __proto__: {
//     constructor: ƒ Person(),
//     sayHello: ƒ sayHello(),
//     watchSitcom: ƒ watchSitcom(),
//     tellYourIq: ƒ tellYourIq()
//   }
// }

// Call the "sayHello()" method:
josh.sayHello()
// Output:
// 'Hello, my name is Josh.'

// Watch some tv show:
josh.watchSitcom()

// Tell me your IQ:
josh.tellYourIq()
// Output:
// 124
Enter fullscreen mode Exit fullscreen mode

The problem with static fields (hint: initialization)

So far, everything looks good. Here comes the problem. The constructor method will not allow you to initialize static fields. This may not be a real problem if all static fields you need can be initialized when you define them. You can achieve this the usual way. You create new static class field and assign it some value.

// Create class:
class Library {
  // Add and initialize static field for books:
  static books = [
    { title: 'Lean UX', read: true },
    { title: 'Lean Customer Development', read: false },
    { title: 'The Four Steps to the Epiphany', read: false },
    { title: 'Lean Analytics', read: false }
  ]

  // Add second static field:
  static booksToReadCount = 3
}

// Log value of "booksToReadCount" field:
console.log(Library.booksToReadCount)
// Output:
// 3
Enter fullscreen mode Exit fullscreen mode

The question is, what if you want to initialize the static field more dynamically? Take the Library class for example. At this moment, it requires manual update of both fields, books and booksToReadCount, to keep them in sync. This might be okay from time to time, but it can quickly become annoying chore.

One could think that this can be solved with the constructor method. You define a static field without initializing it, or initialize it with some placeholder value. Then, you add constructor and use it to update the value of that static field. The problem is that this doesn't work. That field will remain undefined or keep the placeholder value.

class Library {
  // Add and initialize static field for books:
  static books = [
    { title: 'Lean UX', read: true },
    { title: 'Lean Customer Development', read: false },
    { title: 'The Four Steps to the Epiphany', read: false },
    { title: 'Lean Analytics', read: false }
  ]

  // Add static field, but don't initialize it:
  static booksToReadCount

  // Try to initialize static
  // "booksToReadCount" in constructor:
  constructor() {
    this.booksToReadCount = 3
  }
}

// Try to log the value of "booksToReadCount" field:
console.log(Library.booksToReadCount)
// Output:
// undefined
Enter fullscreen mode Exit fullscreen mode

Solution with external resources

One way to solve this problem is to use external resources. You can create new function outside the class that will do what you need. Then, you can assign call to that function to the static field. The value returned by the function will become the value of the static field.

// Create class:
class Library {
  // Add and initialize static field for books:
  static books = [
    { title: 'Lean UX', read: true },
    { title: 'Lean Customer Development', read: false },
    { title: 'The Four Steps to the Epiphany', read: false },
    { title: 'Lean Analytics', read: false }
  ]

  // Add second static field
  // and assign it the returned value of
  // "getBooksToReadCount()" function:
  static booksToReadCount = getBooksToReadCount(Library.books)
}

// Create function to get booksToRead count:
function getBooksToReadCount(books) {
  return books.filter(book => !book.read).length
}

// Log value of "version" field:
console.log(Library.booksToReadCount)
// Output:
// 3
Enter fullscreen mode Exit fullscreen mode

This solution will do the job. The downside is that it requires that external function. If you use this approach multiple times your code can quickly become less clear.

Solution with static initialization blocks

There is an alternative solution. This solution are static initialization blocks coming in ES2022. These static initialization blocks allow you to create code blocks inside the class. You can use these blocks to execute any operation you need. Let's take the Library class as an example again.

You define the class and define the first static field books and assign it with the array of books. Next, you define the second field booksToReadCount, but you don't initialize it. After this, you add the static initialization block. Inside this block, you execute any necessary operation and initialize the booksToReadCount.

class Library {
  // Add and initialize static field for books:
  static books = [
    { title: 'Lean UX', read: true },
    { title: 'Lean Customer Development', read: false },
    { title: 'The Four Steps to the Epiphany', read: false },
    { title: 'Lean Analytics', read: false }
  ]

  // Define static field for count,
  // but don't initialize it:
  static booksToReadCount;

  // Add static initialization block:
  static {
    // Initialize the "booksToReadCount" field:
    this.booksToReadCount = this.books.filter(book => !book.read).length
  }
}

// Log value of "version" field:
console.log(Library.booksToReadCount)
// Output:
// 3
Enter fullscreen mode Exit fullscreen mode

Syntax and rules for static initialization blocks

A few important things about static initialization blocks you should know. First, the syntax. The syntax is very simple. There is the static keyword and code block defined with curly braces ({}). Code you want to execute, including the initialization of static fields, goes inside the code block.

// Create a class:
class MyClass {
  // Add some static field:
  static foo

  // Create static initialization block:
  static {
    // Initialize "foo"
    foo = 'Fizz'

    // And do something more...
  }
}
Enter fullscreen mode Exit fullscreen mode

One interesting thing to mention. Every code block also creates new block scope. So, you can also use these blocks to create temporary variables you may need. These variables will exist only inside the block. Note that this applies only to block-scoped variables const and let. It doesn't apply to var.

// Create a class:
class MyClass {
  // Create static initialization block:
  static {
    // Create some temporary variable:
    const randomNumber = 19

    // And do something...
  }
}
Enter fullscreen mode Exit fullscreen mode

The second thing is that you can have as many static initialization blocks in a class as you need.

// Create a class:
class Dragon {
  // Add static field:
  static trunkVolume

  // Create static initialization block:
  static {
    // Initialize "trunkVolume" field:
    this.trunkVolume = 6_000
  }

  // Add another static field:
  static diameter

  // Create static initialization block:
  static {
    // Initialize "diameter" field:
    this.diameter = 4
  }

  // Add another static field:
  static thrust

  // Create static initialization block:
  static {
    // Initialize "thrust" field:
    this.thrust = 400
  }
}
Enter fullscreen mode Exit fullscreen mode

Third, static blocks are executed when during the execution of initializers of static fields. Fourth and last, if you extend classes, static fields of a superclass will be executed before the static fields of its subclasses.

// Create superclass:
class Vehicle {
  static {
    console.log('Vehicle initialization block.')
  }
}

// Create first subclass:
class Car extends Vehicle {
  static {
    console.log('Car initialization block.')
  }
}

// Create second subclass:
class Plane extends Vehicle {
  static {
    console.log('Plane initialization block.')
  }
}

// Output:
// 'Vehicle initialization block.'
// 'Car initialization block.'
// 'Plane initialization block.'
Enter fullscreen mode Exit fullscreen mode

Conclusion: Class static initialization blocks in JavaScript

If you often find yourself working with static fields you may find static initialization blocks to be a useful feature. They can help make static initialization easier. I hope that this post helped you understand what class static initialization blocks in JavaScript are and how to use them. If you want to learn more about this feature, you can take a look at the proposal available on GitHub.

Discussion (5)

Collapse
darkwiiplayer profile image
DarkWiiPlayer

My first thought is: Why not initialise the variable directly?

const count = this.books.filter(book => !book.read).length
Enter fullscreen mode Exit fullscreen mode

So I assume the real use for this feature is inheritance? If you create a new class that extends Library with a different list of books, will it re-run the same code for the new list? If so, that would be useful.

Also, if these blocks can generally be used to initialised classes, one use-case I'd have is to use this on a wrapper around HTMLElement, to have derived classes automatically register themselves as custom elements.
Currently you need to create a class extending my wrapper, then call a static initialise method to set things up, which I don't quite like.

Collapse
peerreynders profile image
peerreynders

If you create a new class that extends Library with a different list of books, will it re-run the same code for the new list? If so, that would be useful.

ECMAScript class static initialization blocks

No, it has nothing to do with inheritance. Derived classes may benefit from the results of static initialization but each class has it's own initialization and statics have to be called by class name anyway so there is no static "override" mechanism.

Currently you need to create a class extending my wrapper, then call a static initialise method to set things up, which I don't quite like.

Perhaps I'm misunderstanding but why not just register when the module (script) is loaded?

In my view the existence of modules diminishes the need for classes - they tend to be better isolated and more flexible.

Collapse
darkwiiplayer profile image
DarkWiiPlayer

Perhaps I'm misunderstanding but why not just register when the module (script) is loaded?

You are mostly correct, of course, but this is still needless boilerplate. My setup is HTMLElement -> BetterHTMLElement -> MyComponent and I would like BetterHTMLElement to hold all the boilerplate, but currently, after defining class MyComponent extends BetterHTMLElement I will still have to call MyComponent.initialise() for some of the magic to happen, which I don't like. In a perfect world, JS would have hooks for extending classes like ruby does.

No, it has nothing to do with inheritance.

Then this is just one more utterly pointless feature added to the language for bloat and giggles.

Thread Thread
peerreynders profile image
peerreynders • Edited

Then this is just one more utterly pointless feature added to the language for bloat and giggles.

Motivations

For example, if you need to evaluate statements during initialization (such as try..catch), or set two fields from a single value, you have to perform that logic outside of the class definition.

Personally in JavaScript I've always viewed class itself as bloat (composition over inheritance and all that) - but with the introduction of custom elements its existence has been pretty much cemented into JS for all eternity.

When I do feel compelled to use classes they're usually just wrappers using core module functionality expressed in terms of plain objects and functions.

Collapse
imiahazel profile image
Imia Hazel

Thanks for the many new concepts. Great Article.