49

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?

2

7 Answers 7

46

(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.

Sign up to request clarification or add additional context in comments.

6 Comments

Is it allowed static count = 0; in Animal class? It causes SyntaxError: Unexpected token. I'm using Babel with Webpack.
@Caesium133 - As I said above, that's part of the static class fields proposal which is currently at Stage 3 of the process (so, it's not in the spec yet, and engines are only just now looking to add it).
How do you put increaseCount in the constructor of a new Animal? isn't that, ultimately, why people usually want counters on classes? Seems you don't show that case. (tho' i suppose count should really be a property of a collection, not the instance-class -- making count a static property of the class is a kind of 'low-budget' collection, eh?
static count = 0; seems to work these days... am I dreaming? however, it is very different than Java's static
@OhadR - No, you're not dreaming. :-) V8 (in Chrome, Node.js, Brave, Chromium, the new Edge, etc.) and SpiderMonkey (in Firefox 75+) have both started supporting static public fields. Safari is still in progress.
|
14

As mentioned in other answers, this.count refers to instance property in constructor. In order for static property to be initialized, Animal.count should be set.

Class fields proposal provides syntactic sugar for Animal.count = 0 that is available with transpilers (Babel, etc):

class Animal {
  static count = 0;
  ...
}

An alternative in ES6 is to use initial values, in this case Animal.count initial value doesn't need to be set explicitly, e.g.:

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

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

Accessor methods aren't welcome in JavaScript classes - this is what getter/setter descriptors are for:

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

  static get count() {
    return this._count || 0;
  }

  static set count(v) {
    this._count = v;
  }
}

Static-only class is considered antipattern in JavaScript because a state or other traits that are specific to classes aren't used. In case there should be only one instance, plain object should be used (unless there are other concerns that could benefit from class):

const animal = {    
  increaseCount() {
    this.count += 1;
  },

  get count() {
    return this._count || 0;
  },

  set count(v) {
    this._count = v;
  }
};

6 Comments

But aren't your getter and setter functions Accessor methods?
Isn't your getCount an Accessor method?
Your Animal class with getter and setter returns 0 with: let b = new Animal; console.log(Animal.count);
I would say to be more useful, your class with get/set should increment itself when a new instance is created. Your final object style doesn't actually return the value of _count, if _count == 0. It returns 0 when _count is actually undefined. It's a false result. I'd consider that a bug. Since that object requires initializing count to 0 externally, it seems no different than just using a variable. Is an object which acts no different than a variable considered an anti-pattern?
@johnywhy Thanks for noticing, there was a typo, it should be this.count and not this._count in increaseCount and other members besides get/set count. get count() { return this._count || 0 } is mostly the same as _count: 0, get count() { return this._count }. I prefer || 0 because it takes less chars to type and also fixes some invalid values, could be changed to this._count = +v || 0 in set instead for better value conditioning. It depends on the case if it's an antipattern. An object can be extended and is more testable. And its behaviour can be changed any time if needed
|
4

Static class-side properties and prototype data properties must be defined outside of the ClassBody declaration.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

class Animal {

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

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

Animal.count = 0;

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

2 Comments

It's ok but the last code line return 1 , not undefined. It could be misleading for newbie
Today you don't have to declare it outside anymore
2

You can simulate static variables with static getter and setter

 export class MyClass {
  static get variable() {
    return this.foo;
  }

  static set variable(foo) {
    this.foo = foo;
  }
}

And use it like this

  MyClass.variable = 'bar';
  console.log(MyClass.variable);

Comments

1

To set a static variable, set it on the object Animal itself. As of now in Javascript you cannot directly declare static properties inside classes like you can declare static methods.

class Animal {
    constructor() {
    }

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

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

Comments

1

you can use closures to simulate static variables

const Animal= (() => {
    let count= 0;

    class Animal {
        constructor() {}

        static increaseCount() {
            count += 1;
        }

        static getCount() {
            return count;
        }
    }

    return Animal;
})();

console.log(Animal.getCount());
Animal.increaseCount();
console.log(Animal.getCount());

Comments

0

If you want to have incremential ids:

 constructor() {
    super(template);
    if (typeof MyClass.nextId == 'undefined') {
    MyClass.nextId = 0;
    }
    this._id = `${MyClass.nextId++}`;
 }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.