Search code examples
javascriptecmascript-6es6-class

How do I use a static variable in ES6 class?


I'm trying to use a static variable in es6. I'd like to declare a static variable count in Animal class and increase it. However, I couldn't declare a static variable through static count = 0;, so I tried another way like this:

class Animal {
  constructor() {
    this.count = 0;
  }

  static increaseCount() {
    this.count += 1;
  }

  static getCount() {
    return this.count;
  }
}

console.log(Animal.increaseCount()); // undefined
console.log(Animal.getCount()); // NaN

I expected console.log(Animal.getCount()); to be 1, but it doesn't work. How do I declare a static variable and modify it by calling a method?


Solution

  • (Note: Mutable static properties on classes can be problematic when subclasses are involved. See the end of an answer for details on that.)

    Your class has no static variables (if by static variable you mean static property). getCount returns NaN (after you call increaseCount) because Animal has no count property initially. Then increaseCount does undefined + 1 which is NaN, and assigns that to Animal.count. Instances created by new Animal have a count property initially, but Animal itself does not until you call increaseCount. this within a static method refers to the Animal class (constructor function) itself (if you call it via Animal.methodName(...)).

    To give the class a static property, you can declare it with the static keyword (now that the class fields proposal¹ has been widely implemented):

    class Animal {
        static count = 0;
    
        static increaseCount() {
            ++this.count;
        }
    
        static getCount() {
            return this.count;
        }
    }
    
    Animal.increaseCount();
    console.log(Animal.getCount());
    Animal.increaseCount();
    console.log(Animal.getCount());

    If you like, you could make the static member private so it can't be accessed from outside Animal:

    class Animal {
        static #count = 0;
    
        static increaseCount() {
            ++this.#count;
        }
    
        static getCount() {
            return this.#count;
        }
    }
    
    Animal.increaseCount();
    console.log(Animal.getCount());
    Animal.increaseCount();
    console.log(Animal.getCount());


    Regarding the caveat above: Using this within a static method to refer to the class (constructor function) is a bit tricky if there are subclasses, because for instance, if you had:

    class Mammal extends Animal {}
    

    and then

    Mammal.increaseCount();
    

    this within increaseCount (which it inherits from Animal) refers to Mammal, not Animal.

    class Animal {
        static count = 0;
    
        static increaseCount() {
            ++this.count;
        }
    
        static getCount() {
            return this.count;
        }
    }
    
    class Mammal extends Animal {
    }
    
    console.log(Object.hasOwn(Mammal, "count")); // false
    // This call *reads* from `Animal.count` (because `Mammal` doesn't have a
    // `count` yet), but *writes* to `Mammal.count`, potentially causing
    // confusion.
    Mammal.increaseCount();
    console.log(Object.hasOwn(Mammal, "count")); // true
    console.log(Mammal.getCount()); // 1
    // This time, both the read and write use `Mammal.count`
    Mammal.increaseCount();
    console.log(Mammal.getCount()); // 2
    console.log(Animal.getCount()); // 0 - nothing ever wrote to it

    If you want that behavior, use this. If you don't, use Animal in those static methods.

    Finally, I'll note that if you make count a private field, trying to call increaseCount via Mammal will fail, because Mammal can't write to Animal.#count:

    class Animal {
        static #count = 0;
    
        static increaseCount() {
            ++this.#count;
        }
    
        static getCount() {
            return this.#count;
        }
    }
    
    class Mammal extends Animal {
    }
    
    Mammal.increaseCount();
    // ^^^^^^^^^^^^^^^−−− TypeError: Cannot read private member #count from an
    // object whose class did not declare it


    ¹ The class fields proposal was an amalgamation of multiple proposals that were initially separate: Private instance methods and accessors, Class Public Instance Fields & Private Instance Fields, Static class fields and private static methods.