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:
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:
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
// 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. |