Object-oriented Classes in JavaScript

I believe ~ 110% of web developers knows about the existence of JavaScript (JS), but not so many have used it before.

Anyway, this article is not intended for any of them but for JS programmers who never used it as an Object-oriented (OO) language and want to do it - or, at lest, know how you can define classes, methods, modify classes and all its instances in runtime (after all, JS is interpreted code) just as you would in Smalltalk, and even define private stuff so no other objects else can access them.

Why didn't I named this article Object-oriented JavaScript?

Well, I was about to, but then I remembered that it's not the same. In fact, there are some OO languages such as Self which don't even use classes.

But I guess you're not interested in history lessons, so let's get to the point.

Should I read or should I leave?

I'm assumming you have already written some piece of JS in the past, or at least you have some good understanding of its basics. Also, you should have some basic understanding about objects and classes. If you don't, then please first read some tutorial about it, write [working] some code and them come back.

A good place to start with JavaScript is the good old W3Schools.

Regarding OO per-se, I'm not explaining here what's a class, an object, a member, static methods, etc., so you really should have some background on this before going on. If not, you'll most probably get lost along the way.

Do I need classes to work with objects in JavaScript?

No you don't. You can have objects in JS without classes. A good example of this is:

var myPoint = {
  "x": 3.5,
  "y": 8
};
 
alert( myPoint.x );

This will create a new object with two properties (x and y) in variable myPoint. So, as you can see, you don't need classes to deal with objects, but they clearly simplify coding when you want to deal with lots of objects sharing a common behavior.

So what is a class in JS anyway?

Simply put, the way to write classes in JS are functions. No more, no less.

So let's use the traditional Point class as an example.

function Point(xVal, yVal){
  this.x = xVal;
  this.y = yVal;
}
 
var betterPoint = new Point(3.5, 8);
alert( betterPoint.x );

With this you've created your first class and instance of it in JavaScript! Well... sort of, anyway. This is not literally a class but it's the best approach we can have in JS.

So what's this, exactly? Point is the name of a function that assigns xVal and yVal to current object, and if you want to create a new object out of this you need to do what's in the next line: call the function prepending the new keyword.

Now you're effectively creating a new object with two instance members, x and y, initiated with values 3.5 and 8 respectively.

Now, this seems very, very familiar to me... it's a function that creates a new object and initializes it with some values... Of course! It's a Constructor

! :)

Adding some sugar

Ok, we created our first constructor and we can have as many objects as we want with this "mold". It'd be nice to add some methods too, right? Yes, but hang on there... we'll do something else before.

JavaScript is an really dynamic language, in that you can modify objects in runtime. I'm not talking about changing the value of their variables, but adding variables to them. And it's the easiest thing to do:

betterPoint.z = 5;
alert( betterPoint.z );

There you go. Using the previously created object, you can add a new variable by just... typing it and assigning a value! Isn't that nice?

I said I want some methods!

Yeah, I heard you. But, yet again, let's clarify one more thing. Is the following piece of code a valid JavaScript snippet?

var foo = function (){
  alert(1);
};
 
foo();

Yes it is! What you're doing here is creating a variable named foo and assigning a function to it.

Come again? Store a function into a variable. Yes, a function is an object too!

In fact, if you alert that function:

alert(typeof foo);

You'll see a box telling you it's a "function". That is, an instance of class Function. So, after all there are some classes beneath the carpet, huh?

What about adding some methods?

Yeah... if you're reading this, you've earned it. Now that we've been through assigning a function to a variable, this is going to be much easier :)

There are a couple of ways of doing it. Say we want to get the distance between two points. The basic approach would be:

betterPoint.distanceTo = function ( aPoint ){
  return Math.sqrt( Math.pow(this.x - aPoint.x, 2) + Math.pow(this.y - aPoint.y, 2) );
}
 
alert( betterPoint.distanceTo( myPoint ) );

If that Math stuff scares you, forget about it :) With his we've successfully added a new method called distanceTo to the new point we've just created a couple of minutes ago - we assigned a function to a variable called distanceTo.

As we've seen before, a critical piece of knowledge at this point is: functions ARE objects. As as such, they can be stored in any variable, which could allow us to use them as callbacks. But we'll leave that for another article.

Another interesting point here is that we're sending myPoint as an argument of that call, but it's not even been created as an instance of Point. What the hell?

Well, that's what we call Polymorphism: as long as an object "knows" how to answer to the same calls (in this case, accessing its inner variable x), we can send any object there.

Proto - what?

Back to the game. Similarly to what we've done, you could add more variables or methods for just this object if needed. But what about the other instances of this class? Well, if you take this path, they won't even know their brothers and sisters have new new public members, and that's a shame. So let's fix that in a very flexible way: the magic word prototype.

Prototype is the keyword to change all current and future class instances when that class's already been created. So, say we want to add the distanceTo method to class Point. To do it, you'll do something like this:

Point.prototype.distanceTo = function ( aPoint ){
  // Won't write all that Math stuff here again. If you want it, copy and paste it.
}

The interesting point about this approach is that, no matter how many Point instances are out there, prototype lets you magically add a new method to all running instances. That's right: by doing this, you'll be effectively modifying all instances of the class Point. And this can be done with both functions and variables. Why? Again, because functions are object instances of the class Function.

A nice example of this is adding the missing trim() function to the String class:

String.prototype.trim = function() { return this.replace(/^s+|s+$/g, ""); };
 
alert( "    this is a string to be trimmed   ".trim() );

The first line adds the trim method to all strings, so you can call it later from all of them!

Don't move! Static!

Sometimes it's useful to have static class members. For instance, the method distanceTo could be written as a static class member like this:

Point.distanceBetween = function(aPoint, anotherPoint){
  return Math.sqrt( Math.pow(aPoint.x - anotherPoint.x, 2) + Math.pow(aPoint.y - anotherPoint.y, 2) );
}
 
var newPoint = new Point(3, 2);
 
alert( Point.distanceBetween (betterPoint, newPoint) );

Notice how I deliberately omitted the word prototype. Let's be clear again: prototype lets you add instance members, while without it you define static members.

Again, you should be familiar with the "static member" and "instance member" definitions at this point; if not, it's logical that you don't understand this.

I want some privacy!

I do like from time to time to have some private members in my classes, in spite of some saying it's not a good practice (talk with any Smalltaker!). No matter if you just like them or you feel you can't live without them, you can make some of your stuff private! And here's the trick (yeah, it's a lame trick, not an actual language feature, but, hey, it works! :)).

Again, there are several ways to achieve this. One of them would be to redefine our Point class:

function Point(xVal, yVal){
  var self = this;
 
  var x = xVal;
  var y = yVal;
 
  this.label = "Hi, I'm a point!";
 
  this.getX = function (){
    return x;
  }
 
  this.getY = function (){
    return y;
  }
 
  var privateDistanceTo = function (aPoint){
    // usual code using "self" instead of "this",
    // because this function is not a member of this object
    // so "this" does not contain a reference to it.
    // Also, we use getters to access aPoint variables.
    alert(self.label);
  	return Math.sqrt( Math.pow(x - aPoint.getX(), 2) + Math.pow(y - aPoint.getY(), 2) );
  }
 
  this.distanceTo = function (aPoint){
    return privateDistanceTo(aPoint);
  }
}
 
var betterPoint = new Point(3.5, 8);
var newPoint = new Point(3, 2);
 
// Shows "3".
alert( newPoint.getX() );
 
// Shows "undefined", since it's a private variable.
alert( newPoint.x );
 
// This works like a charm, because distanceTo can access privateDistanceTo :)
// First this shows the label (note the "alert" inside privateDistanceTo),
// then the distance between the two points.
alert( newPoint.distanceTo(betterPoint) );
 
// This would throw an JS error and stop executing the whole script,
// since the private function is not visible, it's undefined,
// and you're acting as if you could call it.
alert( newPoint.privateDistanceTo(betterPoint) );

Several things to note here:

  • Definitions of variables x and y were changed from this.x to var x. This is what makes these variables private to this function, so nobody else can even take a look.
  • There a new private variable called self. What's it good for? You'll need it to keep a reference to the present object everytime you call the private methods, which are not defined to be a part of "this". If you used this instead, they would reference to who knows what but the object you really want.
  • Since you cannot longer access variables x and y directly, there are two new getter methods to fetch their values. Note how they are defined inside of "this" and they can access those external variables.
  • I added a "label" variable just to make a stronger point about the difference between private variables x and y and this public variable label. The difference, again, is in the way you define them: "this" vs. "var".
  • Last, but not least, note how distanceTo can access to privateDistanceTo, but it cannot be accessed from the outside.

Wrapping it up

As you can see, JavaScript lets you play with objects in a very flexible way, giving you a lot of power. To be completely honest, we didn't cover every possible way of achieving our goals, so, finishing this article, I'll write a possible implementation of the Vector class with some minor modifications and both public and private members. I will leave you the fun part - to understand it!

function Vector (xVal, yVal, labelVal) {
  var _private = {
 
    "x": xVal,
 
    "y": yVal,
 
    "label": labelVal
 
  }; // _private
 
 
  var _public = {
    "getX": function (){
      return _private.x;
    },
 
    "getY": function (){
      return _private.y;
    },
 
    "getCoordinates": function (){
      return "( " + _private.x + " , " + _private.y + " )";
    },
 
    "getLabel": function (){
      return _private.label;
    },
 
    "setX": function (xVal){
      _private.x = xVal;
    },
 
    "setY": function (yVal){
      _private.y = yVal;
    },
 
    "setLabel": function (labelVal){
      _private.label = labelVal;
    },
 
    "distanceTo": function (aVector){
      return Math.sqrt( Math.pow(_private.x - aVector.getX(), 2) + Math.pow(_private.y - aVector.getY(), 2) );
    },
 
    "normalize": function (){
      var center = new Vector(0, 0);
      var length = this.distanceTo(center);
 
      _private.x = _private.x / length;
      _private.y = _private.y / length;
    }
  }; // _public
 
  return _public;
}
 
 
// Testing
firstVector = new Vector(3, 6, "I'm the new vector");
secondVector = new Vector(-2, 2, "Now, I'm the newest!");
 
alert( "firstVector coordinates: " + firstVector.getCoordinates() );
alert( "firstVector says: " + firstVector.getLabel() );
 
alert( "secondVector coordinates: " + secondVector.getCoordinates() );
alert( "secondVector says: " + secondVector.getLabel() );
 
secondVector.setLabel( secondVector.getLabel() + "!!!" );
alert( "Correction... secondVector says: " + secondVector.getLabel() );
 
alert( "Distance from 1st to 2nd = " + firstVector.distanceTo(secondVector) );
alert( "Distance from 2nd to 1st (should be the same) = " + secondVector.distanceTo(firstVector) );
 
firstVector.normalize();
firstVector.setLabel("I'm shorter now :(");
alert( "firstVector says: '" + firstVector.getLabel() + "'" );
alert( "firstVector coordinates: " + firstVector.getCoordinates() );
 
alert( "Distance from 2nd to 1st = " + secondVector.distanceTo(firstVector) );

Hope you liked article :)

Now go create some objects classes!

Post new comment

The content of this field is kept private and will not be shown publicly. If you have a Gravatar account, used to display your avatar.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.