TOC
Classes:

Static fields & methods

So far, all the class members (fields and methods) we have used in this tutorial are so-called instance members. This means that while they are declared on the class, you need an instance of the class (an object) to access this member.

This makes perfect sense - just consider our previous Dog class with the Name field, for storing the name of the dog. Typically you would want to create more than one instance of a dog, so the Name would have to belong to the instance of the class and not the actual class.

However, sometimes it makes sense to define members on a class which are not bound to an instance of the class. Members that are not instance members are called static members.

Static methods

Most commonly used are probably static methods, often used for utility functions not specific to an instance of the class, but related enough to exist on the class. An example of this is the so-called "factory methods", used for creating instances of the class, usually based on some logic which would make sense to have in its own function.

To show you an example of a static method in action, I will add a method to our Dog class from the previous articles, to create a new dog with a random age. This might not be terribly useful in the real world, but hopefully it will give you an idea of how and when to use this technique. First, here's the method I will be adding to the class:

static CreateDog(name)
{
	let dog = new Dog();
	dog.name = name;
	dog.age = Math.floor(Math.random() * 20) + 1;
	return dog;
}

Notice that the name of the method is prefixed with the static keyword, obviously marking this method as a static method. Inside of it, we create a new instance of a Dog, assigns the passed in name and then we create a random number for the age field.

When calling a static method, whether from within the class or outside of it, you always specify the full class name first, then a period, and then the name of the method. It would look like this:

Dog.CreateDog("Pluto");

Now let's see the complete example, including the static method:

class Dog
{
	name;
	age;
	
	constructor(name, age)
	{
		this.name = name;
		this.age = age;
	}
	
	static CreateDog(name)
	{
		let dog = new Dog();
		dog.name = name;
		dog.age = Math.floor(Math.random() * 20) + 1;
		return dog;
	}
	
	Describe()
	{
		return this.name + " is " + this.age + " years old";
	}
}

let dog = new Dog("Dog Doe", 7);
alert(dog.Describe());

let randomizedDog = Dog.CreateDog("Pluto");
alert(randomizedDog.Describe());

As you can see from the last lines of the example, we can still create a Dog like normal, but as an alternative, we now have the CreateDog() method to get a dog with a random age.

Static fields

We also have static fields, which are useful for sharing the same piece of data not specific to a single instance, e.g. cached data and/or data to be used by static methods. Let's try expanding the example above, to use a couple of static fields, but first, let's see how a static field is defined:

class Dog
{
	name;
	age;
	
	static dogNames = ["Dog Doe", "John Dog", "Pluto"];
	static dogCounter = 0;
	...

As you can see, the static fields are just prefixed with the static keyword, just like static methods, and they can be declared along regular fields. Just remember that when you declare a static field, this field is now shared between all instances of the class. As you'll see from the full example below, this can be very useful.

In this case, I have declared two static variables: One for keeping track of how many dogs we have created, and one is a list of possible dog names. The latter will be used to turn the CreateDog() into a completely parameter-less function, which randomly picks the name from the list of dog names and then assigns a random age as well. Let's see how it works:

class Dog
{
	name;
	age;
	
	static dogNames = ["Dog Doe", "John Dog", "Pluto"];
	static dogCounter = 0;
	
	constructor(name, age)
	{
		this.name = name;
		this.age = age;
		Dog.dogCounter++;
	}
	
	static CreateDog()
	{
		let dog = new Dog();
		dog.name = Dog.dogNames[Math.floor(Math.random() * Dog.dogNames.length)];
		dog.age = Math.floor(Math.random() * 20) + 1;
		return dog;
	}
	
	Describe()
	{
		return this.name + " is " + this.age + " years old";
	}
}

let dog1 = Dog.CreateDog();
alert(dog1.Describe());

let dog2 = Dog.CreateDog();
alert(dog2.Describe());

let dog3 = Dog.CreateDog();
alert(dog3.Describe());

alert("Total dogs: " + Dog.dogCounter);

As you can see, the CreateDog() method is now completely parameter-less - it requires no input, but simply returns a dog with a name from our list, as well as a random age.

You will also notice that we use the static dogCounter field in the constructor, where we increment it by one. This allows us to always keep track of how many dogs have been created. We use this field in the last line of the example, and as you can see, just like a static method, we access it by referencing the class name, then a period and then the name of the field.

Static initialization blocks

As the last subject for this article, I would like to show you the concept of static initialization blocks. They are basically like constructors, but for static fields. So for instance, we can define a static field AND assign a value to it like this:

class Dog
{
	static dogNames = ["Dog Doe", "John Dog", "Pluto"];
	...

But what if we wanted to do something a bit more complex than just assigning one or several static values to the field? There could be many reasons for this, and thanks to the static initialization block, we can add any kind of logic to the initialization of our static fields, including try..catch blocks for error handling, initializing multiple fields etc.

A static initialization block is added to a class simply by using the static keyword, followed by a code block, like this:

class Dog
{
	static 
	{
		// Static initialization block
	}
	...

You can have as many of these blocks as you'd like - they are simply processed from start to end. However, in most cases, you'll likely just need the one. Also notice that the static initialization block is called before the actual constructor, allowing you to rely on your static fields as soon as you start using the class.

To show you how the static initialization block works in action, I have rewritten our Dog class quite a bit. It now uses a list of completely randomly generated names as the list of possible dog names. I admit that this class is starting to look a bit silly and overly complex, but just bear with me - it hopefully proves how powerful these features are and how they can be used.

And just to further illustrate how flexible the JavaScript language is, I have added a function, inside the static initialization block, to generate the random string we will use as a dog name:

class Dog
{
	
	name;
	age;
	
	static dogNames;
	static dogCounter;
	
	static 
	{
		alert("Initializing static fields...");
		
		function GenerateDogName() 
		{
			let result = "";
			const chars = "abcdefghijklmnopqrstuvwxyz";
			const charsLength = chars.length;
			const dogNameLength = 8;
			let counter = 0;
			while (counter < dogNameLength) 
			{
			  result += chars.charAt(Math.floor(Math.random() * charsLength));
			  if(result.length == 1)
			  	result = result.toUpperCase();
			  counter += 1;
			}
			return result;			
		}
		
		this.dogNames = [];
		while(this.dogNames.length < 5)
			this.dogNames.push(GenerateDogName());
		this.dogCounter = 0;
	}
	
	constructor(name, age)
	{
		this.name = name;
		this.age = age;
		Dog.dogCounter++;
	}
	
	static CreateDog()
	{
		let dog = new Dog();
		dog.name = Dog.dogNames[Math.floor(Math.random() * Dog.dogNames.length)];
		dog.age = Math.floor(Math.random() * 20) + 1;
		return dog;
	}
	
	Describe()
	{
		return this.name + " is " + this.age + " years old";
	}
}

let dog1 = Dog.CreateDog();
alert(dog1.Describe());

let dog2 = Dog.CreateDog();
alert(dog2.Describe());

let dog3 = Dog.CreateDog();
alert(dog3.Describe());

alert("Possible dog names: " + Dog.dogNames);
alert("Total dogs: " + Dog.dogCounter);

With this final modification, our Dog class now generates a list of completely random dog names and stores them as a static field. When you instantiate a new Dog object, it uses a randomly selected name from the list of randomly generated names, while also keeping track of how many dogs have been created.

All the new stuff happens in the static initialization block, where we have created a function called GenerateDogName(). It simply produces a set of 8 random characters, which we'll use as a name. In the lower part of the static initialization block, I call the GenerateDogName() in a while loop, to add 5 random names to our dogNames array.

Again, at this point, this is quite a silly example, but it does show several techniques that could prove very useful in various cases.

Summary

A class can have static members in the form of static fields and static methods. Unlike instance members, static members are referenced directly on the class insted of on an instance of the class. They are generally used for sharing the same data between instances of class (static fields) and for utility functionality relating to the class (static methods).

Static fields can be declared just like regular fields, and values can be assigned to them when declaring them as well, but as an alternative, you may use a static initialization block to assign more complex values to the static fields.