To improve something, you have to measure it first.
That goes for your money as well.
GreaterThanZero.com


Page 6 of: Object-Oriented Programming in JavaScript Explained, by Thomas Becker   about me  

Static Members

The class Point that we started with in Section 3 was of course not fully representative of a class in a traditional OO language. One thing that was missing were static members (fields and methods). Let us assume that we want to instrument our code with counters that tell us how many times each of the two methods was called. Moreover, we want a way to reset all counters to zero. This is what the class Point would now look like:
class Point {

  // Call counters
  private static int distanceCounter = 0;
  private static int getLabelCounter = 0;

  // x- and y-coordinate
  private double x;
  private double y;

  // Constructor from coordinates
  public Point(double x, double y) {
    this.x = x;
    this.y = y;
  }

  // Distance between this and another point
  public double distance(Point other) {
    ++distanceCounter;
    return Math.sqrt(Math.pow(this.x - other.x, 2) +
      Math.pow(this.y - other.y, 2));
  }

  // Get label, like "(1.00, -1.00)"
  public String getLabel() {
    ++getLabelCounter;
    DecimalFormat coordinateFormatter = new DecimalFormat("#.##");
    return new StringBuilder("(")
      .append(coordinateFormatter.format(x))
      .append(", ")
      .append(coordinateFormatter.format(y))
      .append(")").toString();
  }

  // Get distance counter
  public static int getDistanceCounter() {
    return distanceCounter;
  }

  // Get getLabel counter
  public static int getGetLabelCounter() {
    return getLabelCounter;
  }

  // Reset all call counters
  public static void resetCallCounters() {
    distanceCounter = 0;
    getLabelCounter = 0;
  }
}
How would we achieve the same thing in pre-ECMAScript-6 JavaScript? First off, static fields are characterized by two properties:
  • They exist only once for the class, indepently of the existence of any instantiations.
  • They are accessible via the class name, in the absence of any instantiations.
That's a no-brainer in pre-ECMAScript-6 JavaScript: just put them in the prototype. In our example, we'll say
Point.prototype.distanceCounter = 0;
Point.prototype.getLabelCounter = 0;
That way, the counters will be created only once, not on a per-object basis, and they will be accessible from the constructor as well as from an object:
  alert(Point.prototype.distanceCounter);
  var p = new Point(0.0, 0.0);
  alert(p.distanceCounter);
The access via the constructor, as in the first line of code above, is of course the preferred way of accessing a static field. But the access via an object works as well, due to property lookup in the prototype.

So what about static methods? They are characterized by these properties:

  • They have access to static fields, but not to non-static ones.
  • They are accessible via the class name, in the absence of any instantiations.
Apart from a certain lack of enforcement, which we'll discuss in a moment, this can again be achieved by putting them in the prototype:
Point.prototype.resetCallCounters = function() {
    Point.prototype.distanceCounter = 0;
    Point.prototype.getLabelCounter = 0;
  };
We can now call resetCallCounters via the constructor, in the absence of any objects, as well as from an object:
  Point.prototype.resetCallCounters();
  var p = new Point(0.0, 0.0);
  p.resetCallCounters();
Again, the call via the constructor is the preferred one. And here's the complete pre-ECMAScript-6 JavaScript version of Point, with the new static members:
//
// The equivalent of the point class in pre-ECMAScript-6 JavaScript
//

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.distance = function(other) {
  ++Point.prototype.distanceCounter;
  return Math.sqrt(Math.pow(this.x - other.x, 2) +
    Math.pow(this.y - other.y, 2));
}

Point.prototype.getLabel = function() {
  ++Point.prototype.getLabelCounter;
  return "(" + this.x.toFixed(2) + ", " + this.y.toFixed(2) + ")";
};

Point.prototype.distanceCounter = 0;
Point.prototype.getLabelCounter = 0;

Point.prototype.resetCallCounters = function() {
    Point.prototype.distanceCounter = 0;
    Point.prototype.getLabelCounter = 0;
  };
Since all fields are public in JavaScript, we don't need the getters for the counters. Some people would add them as a matter of style.

I mentioned earlier that JavaScript's way of modeling static methods lacks enforcement. Indeed, when you look at the static and non-static methods in the Point example, you see that they just sit there side by side in the prototype object. The only thing that distinguishes them from each other is the fact that static methods, via programming discipline, do not access non-static fields. There is nothing that prevents us from calling a non-static method as if it were static, as in

// Bad: non-static method called as if it were static
var label = Point.prototype.getLabel();
This doesn't make sense because the receiver of the function call is Point.prototype. Therefore, the properties x and y that are referenced in the body of getLabel will be undefined, unless, of course, somebody has added properties named x and y to the global object, which would be even worse. Similarly, nothing prevents somebody from altering a static method and make it access non-static fields, thereby effectively turning it into a non-static method. Existing calls to that function through an object would continue to work just fine, while calls via the constructor would become non-sensical.

Examples of this kind of laxness abound in JavaScript. As a matter of fact, we've seen another one earlier in Section 4: constructor functions can be called in a manner that results in non-sensical behavior. If this kind of thing really bothers you, JavaScript is not for you. Personally, I'm a bit on the fence in this regard. I do miss the kind of enforcement that traditional OO languages like Java afford. On the other hand, experience shows that in the long run, enforcement of design principles, no matter how reasonable they are, contributes surprisingly little to software quality.