20

I am trying to figure out alternative ways to set a static (or class) property an ES6 Class and then change it after new instances of the class are created.

For example, lets say I have a class called Geo, and I need a static property called all that will give me the array of all instances of the Geo class.

This version works:

class Geo {
  constructor(name){
    this.name = name;
    Geo.all.push(this);
  }
}

Geo.all = [];

ruby = new Geo("Ruby");
rocks = new Geo("Rocks");
console.log(Geo.all.length); // => 2

I would prefer to not set the property OUTSIDE of the class definition though. I've tried a few things but can't seem to create a static property within the class that I can update from the constructor.

I should also mention I need to be able to do this in the browser (Chrome) without use of Babel or similar.

Here are examples of some things I've tried:

class Geo {
  constructor(name){
    this.name = name;
    Geo.all.push(this);
  }
  static get all() {
    return [];
  }
}

ruby = new Geo("Ruby");
rocks = new Geo("Rocks");
console.log(Geo.all.length); // => 0 

And another

class Geo {
  constructor(name){
    this.name = name;
    Geo.all.push(this);
  }

  static all = [];
}

ruby = new Geo("Ruby");
rocks = new Geo("Rocks");
console.log(Geo.all.length); // => error unexpected "="
brasofilo
  • 25,496
  • 15
  • 91
  • 179
Jared
  • 631
  • 1
  • 7
  • 19
  • 1
    There is no other way in ES6 to do that. – Bergi May 20 '17 at 21:58
  • 2
    What's wrong with `Geo.all = []`? – dfsq May 20 '17 at 21:58
  • 4
    You should not have a global collection of instances - this will lead to memory leaks and modularisation (e.g. testability) problems – Bergi May 20 '17 at 21:58
  • You could do `Geo.all = (Geo.all || []).concat(this);` within the constructor function. The static initializer outside the class definition is however the 'idiomatic' standard way to solve this. It also guarantees existence of `Geo.all` even before the first instance is created. – le_m May 20 '17 at 22:25
  • Do you understand that a static property has one value for all instances. It's a class property, not an instance property. If you want to create an instance variable from a static property, you can copy the static property to an instance property at whatever time you want to grab the static property value and from then on the instance property will be maintained separate on that instance. But, a class property has only one value for all instances. – jfriend00 May 20 '17 at 23:08
  • 1
    If you want to keep track of all instances of a particular object, then you probably don't want to put them in an array because that will likely lead to memory leaks because doing so forces your code to manage memory for every object (you have to manually remove it from the array in order for it to be freed by the garbage collector which kills a lot of the usefulness of garbage collection). You could put them in a `weakMap` or `weakSet` depending upon how you want to be able to access them and that won't kill garbage collection. – jfriend00 May 20 '17 at 23:10
  • What is wrong with doing something like creating a variable called GeoInstances, apart from the class, and use that? The class should, just like this variable exist within it's own scope (not the global scope). So it will be private to the block they exist in. Actually, indeed: why then not just use Geo.all? – MPS May 20 '17 at 23:12

5 Answers5

21

There's no such thing as static all = [] in ES6. Class instance and static fields are currently stage 3 proposals which can be used via a transpiler, e.g. Babel. There's already existing implementation in TypeScript that may be incompatible with these proposals in some way, yet static all = [] is valid in TS and ES.Next.

Geo.all = [];

is valid and preferable way to do this in ES6. The alternative is getter/setter pair - or only a getter for read-only property:

class Geo {
  static get all() {
    if (!this._all)
      this._all = [];

    return this._all;
  }

  constructor() { ... }
}

Tracking instances in static property can't generally be considered a good pattern and will lead to uncontrollable memory consumption and leaks (as it was mentioned in comments).

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
4

This works for me for static properties.

  class NeoGeo {

    constructor() {

    }

    static get topScore () {
      if (NeoGeo._topScore===undefined) {
        NeoGeo._topScore = 0; // set default here
      }

      return NeoGeo._topScore;
    }

    static set topScore (value) {
      NeoGeo._topScore = value;
    }

  }

And your example:

  class NeoGeo {

    constructor() {
      NeoGeo.addInstance(this);
      console.log("instance count:" + NeoGeo.all.length);
    }

    static get all () {

      if (NeoGeo._all===undefined) {
        NeoGeo._all = [];
      }

      return NeoGeo._all;
    }

    static set all (value) {
      NeoGeo._all = value;
    }

    static addInstance(instance) {
      // add only if not already added
      if (NeoGeo.all.indexOf(instance)==-1) {
        NeoGeo.all.push(instance);
      }
    }
  }

Note: In the getter you could also check for the existence of the property using the in keyword or the hasOwnProperty keyword.

    static get topScore () {
      if (!("_topScore" in NeoGeo)) {
        NeoGeo._topScore = 0; // set default here
      }

      return NeoGeo._topScore;
    }

And using hasOwnProperty:

    static get topScore () {
      if (NeoGeo.hasOwnProperty("_topScore")==false) {
        NeoGeo._topScore = 0; // set default here
      }

      return NeoGeo._topScore;
    }
1.21 gigawatts
  • 16,517
  • 32
  • 123
  • 231
0

I recently had a similar issue of creating static classes.

I first tried it with constant class variables, but Chrome debugger threw an error. So I defined the class variables 'static', also the getter methods.

Worked in Chrome.

class TestClass {
  //static properties.
  static _prop1 = [ 'A', 'B', 'C'];
  static _prop2 = true;
  static _prop3 = 'some String';
  
  //constructor. Commented out because the class only has static elements.
  //constructor () {}
  
  //Getters.
  static get prop1 () {
    return this._prop1;
  }
  
  static get prop2 () {
    return this._prop2;
  }
  
  static get prop3 () {
    return this._prop3;
  }
}
0

The only way to properly add a getter is to extend the class and use that extended class.

class Basic {
  get firstGetter() {
    return 'firstGetter'
  }
}
class ExtendedClass extends Basic {
  get firstGetter() {
    return 'updatedFirstGetter'
  }
}

}

evanjmg
  • 3,465
  • 1
  • 14
  • 12
0

Update your node to the version 12 or up and that's it ;)

pedro.caicedo.dev
  • 2,269
  • 2
  • 16
  • 19