Friday, April 3, 2015

Stay Out Of My Objects!

While working on my game for the GitHub Game Off this week, I was looking in the code for Crafty.js. Crafty is the JavaScript game engine used by the game I forked for the contest, and I was curious about how it did some things. I noticed some JavaScript features it in that I don't see used very often and I thought it was interesting enough to share.

Have you ever wanted to make a field in a JavaScript object read-only? Consider this example:

var o = {
    answer: 42
};

console.log(o.answer); // prints 42
o.answer = 84;
console.log(o.answer); // prints 84

Typically, you might not care that you can reassign answer to some other value. Perfectly normal, right? Sure. But what if you were making a library that you want to share and be used by other people in other projects? You might not want them accidentally (or on purpose) messing with your object internals.

So try this:

var o = {};

Object.defineProperty(o, 'answer', {
    value: 42,
    writable: false
});

console.log(o.answer); // prints 42
o.answer = 84;
console.log(o.answer); // prints 42 again! ha!

Now you'll see that the answer of 42 prints out both times. The reassignment had no effect. You've created a read-only field on the object!

The JavaScript function Object.defineProperty gives you much more control over the properties on your objects. In addition to writable, you can also specify if a field should be enumerable. Being enumerable means that the property shows up during property enumeration of the object. Look at this example:

var o = {};

Object.defineProperty(o, "answer", {
    value: 42,
    writable: false,
    enumerable: false
});

Object.defineProperty(o, "whatnot", {
    value: 33.3,
    enumerable: true
});

Object.defineProperty(o, "here", {
    value: "there",
    enumerable: true
});

for (var i in o) {
    console.log(i);
}

The example above will output "whatnot" and "here" but not "answer." The answer field does still show up if you console.log(o) in modern Chrome, however. It will also be visible in the debugger, so this is more about hiding things from other code that it is from people.

The answer can be seen here, but not enumerated.

There's another neat trick you can do with Object.defineProperty, which is create getter and setter functions. Instead of just getting or setting a field's value in your code, defining a custom getter or setter means that a function is invoked which can take other actions. Here is an example:

var o = {
    theAnswer: 42,
    answersGiven: 0
};

Object.defineProperty(o, "answer", {
    get: function() {
        console.log("giving an answer");
        this.answersGiven++;
        return this.theAnswer; 
    },
    set: function(a) {
        console.log("changing the answer!");
        this.theAnswer = a * 2;
    }
});

var myAnswer = o.answer;
console.log(myAnswer); // prints 42

o.answer = 15; // changes the answer, but not to 15!

var myNextAnswer = o.answer;
console.log(myNextAnswer); // prints 30

console.log(o.answersGiven); // prints 2

In the code above, the special getter function is called and an internal counter is incremented each time o.answer is invoked. Similarly, assigning o.answer to a value calls the setter function which has its own ideas about what value should really be set.

I don't know if these things will ever be useful to you, but at the very least I hope you found them as interesting as I did. Always look in the code for open-source libraries! You never know what cool tricks you'll see in them.

Remember to try out my game at http://caseyleonard.com/ggo15 and give me some feedback. I still have over a week left to work on it!

There's one thing that bothers me now, though. Why is there a w in answer?

Amphibian.com comic for 3 April 2015