Strongly Typed Properties for JavaScript
While coding in JavaScript I’ve often wished I could enforce the type of some variables. The typical case where I’ve felt the need for that is when some mathematical operation or comparision is done on a non-number, yielding wierd results but not throwing an error.
While tinkering with a hobby project (an HTML5 game) I repeatedly run into the case where
a NaN
was found where a number was expected. NaN
is usually the result of a mathematical operation gone wrong,
is of type number, and is hard to reason about.
Consider the following:
sprite.y = sprite.y + sprite.velocityY;
If sprite.velocityY is undefined or null
, sprite.y will be NaN
and the JavaScript interpreter won’t complain. I just can’t
understand why the sprite on screen isn’t moving.
This is the type of error I encountered. At first I tried to track such bugs down by placing checks everywhere. However, it’s just after-the-facts-checking and it didn’t tell me where the value was corrupted.
The Solution
Enter Object.defineProperty. Most browsers support this feature today. When constructing an object, you can create a property with a getter and setter that acts just like a normal variable, but you have the opportunity to inspect the value. If you wanna apply this to a class, apply it to the prototype. Consider the example with the sprite:
// Class definition
function Sprite() {
// Constructor code ...
};
Object.defineProperty(Sprite.prototype, "y", {
set: function(val) {
if (typeof val !== "number" || isNaN(val)) {
throw "Sprite.y must be a valid number and not NaN";
}
this.__y = val;
},
get: function() {
return this.__y;
}
})
Sprite.prototype.y = 0;
Sprite.prototype.x = 0;
// Instantiate
var sprite = new Sprite();
sprite.x = "Hello, world!"; // Passed without complaints
sprite.y = sprite.somethingUndefined / 10; // Exception is thrown
…and suddenly you have an object with a strongly typed property.
Wrapping this in a function would look something like:
function typedProp(obj, key, type) {
if (!Object.defineProperty) { // Make older browsers not crash
return;
}
Object.defineProperty(obj, key, {
set: function(val) {
if (typeof val !== type || (type === "number" && isNaN(val))) {
throw "Property '" + key + "' should be of type '" + type + "', value is: '" + val + "' of type '" + typeof val + "'";
}
this["__" + key] = val;
},
get: function() {
return this["__" + key];
}
});
}
// ... and augment the Sprite prototype:
typedProp(Sprite.prototype, "name", "string");
sprite.name = 31337;
Since you might wanna avoid running this in a production environment, you could just have typedProp be an empty function in your release build.
At least you should check for the presence of Object.defineProperty
.
Some Syntactic Sugar
In my own class library, I implemented the functionality to be used as such:
CB.Class("Sprite", {
"x:number": 0,
"y:number": 0,
"name:string": "defaultname",
initialize: function() {
}
// More code goes here
});
What happens here is that I added some syntactic sugar that lets you define properties in ActionScript-like-style. Yes, you can have colons in property names. The part after the colon is stripped off and used as the type. Use my library if you want to: git@github.com:zufallsgenerator/cbclass.git, but you probably have itchy finger to build your own.
Happy coding!
Edit: strictly typed -> strongly typed Edit: fixed typo _Edit: fixed http -> https