JavaScript Classes: A primer focusing on encapsulation

posted in: HOWTO | 0

Preface on target audience:
This post is intended for someone who is mostly comfortable with JavaScript and is familiar, at a basic level, with object-oriented-programming principles. If you can create functions and arrays in JavaScript, and know what “this” means in the context of classes, then that should be adequate.

For several months, I had been searching for a good guide on writing JavaScript classes; something that started very basically and used lots of examples. What I found were guides on writing plugins in jQuery, doing inheritance with JavaScript, overly technical articles about the underlying framework behind JavaScript’s prototype method (lots of discussion about the prototype property — which was confusing for a hot minute since there is also a framework named Prototype.)

I know they’re out there, but I’ve had a hard time finding them. All I really wanted was a guide on basic encapsulation to make my code cleaner, but so many of the guides focused instead on the Inheritance aspect (which is arguably flashier / sexier).

With the caveat that I would never claim to be a JavaScript expert, and also that I have only just recently figured this out in detail, I’d like to share my findings and process. My hope is that this can be a stepping stone for some others out there like me. I’m open to feedback on this, but want the focus to be on both encapsulation and keeping it basic.

The following steps all presume that you have an editor, a browser, and Firebug or some similar debugger running.

The tutorials I typically see use some sort of abstract concept like “car” or “animal”, but I want to use something more practical. We’re going to create a “Die” (as in singular of “dice”) class. It will have a factory, some basic methods and properties, and also be tied directly to some DOM elements. This last part is quite possibly violating some sort of rule or is an antipattern or something. I don’t know. But I know that when I do this in my skunkworks projects, it makes my life easier.

Getting Started

Let’s get this out of the way: in JS, a class is a function. There are some additional things we do that make it specialized, but at its core, it is a function. So we’ll start with this:

Alternately, you can do it the more modern(?) way, as an anonymous function:

Whatever. I’m going to favor the latter method because it makes some things easier to do later on. Just do one of the two above and continue.

As it stands, you can call Die(), but it does nothing. You can even do something like var d = new Die(); but it will do nothing other than return an object. Let’s bring in the prototype property. Following the previous code, add:

The “prototype” property is available to all JavaScript variables, I think. Through this property, you can establish the methods, fields, properties, etc. that are available to instantiations. The syntax of this is a little crazy, but for now, just trust me. Be sure you get those closing parens pair, too. You may want to shorten that snippet to this instead:

That’s it! While this really does nothing, that form is adequate to begin fleshing out the class. It provides the wrappers we need to do proper encapsulation.

Creating a Constructor and an Instance Method

So the constructor definition is actually that first function we defined. But we need to inform the prototype that this is the case. Modify the first part to now say (I’m using the anonymous function version here):

Now we’ll tell the prototype, modifying the second part to read:

Note that constructor is an Officially Recognized Keyword™. In your JavaScript console of choice (I recommend Firebug, but that’s just what I’m familiar with), you can do this:

Which will yield:

We need to add a Roll() method. My personal preference with this is to make public “primary” methods begin with an uppercase letter, and private auxiliary / helper methods begin with a lowercase letter. This is completely arbitrary, as is my capitalization of “Die” as the class name. Do whatever you are familiar with; it’s your code.

The Roll() method will be added to the prototype declaration.

You can test this out in your console with:

Additional public methods can be added in a similar fashion. Be sure to separate each function addition by commas.

Let’s try a static variable.

Static Variables

Let’s do one static variable that tracks rolls and another that prints out a histogram, so if we have multiple dice we can get the totals for all dice.

Static elements are added outside of the prototype declaration, but namespaced to your class. So if we wanted to add a rolls static property that was an array, and a Instagram Histogram static method, we would do this, either before or after the prototype definition (I don’t think it matters, as long as it’s outside of the prototype block):

Let’s modify the Roll() method to store its results:

Note that the “+ 1” was kept with the “return” line rather than the calculation.

If you try to run this and console.log() the die object, the results array will show NaN for the entry. This is a JavaScript issue that we can easily resolve.

In the constructor, change it to read this instead:

This just instantiates each of the sides to be “0” if it’s presently undefined. The nice thing about this is that it will automatically adjust the size of the array to fit the largest die we’ve instantiated.

For the Instagram Histogram method, we’ll just do something simple for now:

Now let’s bring in the big guns: we’ll risk pissing off MVC-pedants and mix our view with our model and controller.

Tying it to the DOM

I’m going to use jQuery here. FYI. Use whatever you like. Note that using a console will be inadequate here, as it involves working with the DOM and storing event handlers and whatnot. You’ll need to do this in a separate file and load it in your browser directly. Also — some of the javascript will need to be wrapped in a $(document).ready(); wrapper (or its equivalent, if you aren’t using jQuery).

I typically create an Init instance method to handle the initial tying to the DOM. I also like to store a handle to the DOM node in an instance variable (let’s call it something completely arbitrary and random, like node). We’re also going to create a static count of how many Dice have been created in the DOM so that we can always assign a unique ID to them:

Before the prototype block:

In the prototype block:

In the main HTML document, we’ll use a fancy new HTML5 button tag.

Now we need to tell it to update the value of our button whenever we do the Roll() method. Change the Roll() method to:

One last thing we’ll do is to use jQuery to bind the click event to the button. This will be done in the Init() method:

This last bit of cleverness uses the trick of assigning the current object to a separate variable, since this changes context with scope.

Creating a Factory

The point of a Factory, in this case, is to easily generate all of the components necessary to have a fully-functioning page widget; HTML and all, ready to use, out of the box. We want to be able to say:

And have a new Die button appear as if by magic, appended to parentDiv. This will be a static, method, so add this outside the prototype block.

Final Script

The final script looks like this:

That’s it!

When you load the page, you should see 2 empty square buttons. Clicking the button will “roll” the die and store the result in the button.

In the console, you can use Instagram Histogram, after rolling both dice several times, to see a result of all dice rolls:

Since the methods are all encapsulated into this one class, you can easily add new Dice

You can also easily alter the methods slightly without having a lot of extraneous code, etc. Tying those objects directly to DOM elements, since they will be used in that fashion, helps to keep the code consistent. As I said earlier, this is kind of an anti-pattern with regard to the MVC model, but if you take care to not be overly specific with how you interact with the DOM (ie. use classes / arguments for selectors, manipulate the presentation through CSS changes, and try to keep any additions or deletions to the DOM internally consistent in the class, if possible), it can work pretty well.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.