Friday, March 15, 2013

Understanding JavaScript Objects

Sometimes I think JavaScript is a very misunderstood language. I think this stems from the fact that a lot of people (myself included) started using JavaScript as a means to complete quick and dirty tasks in browsers. As the web underwent transformations (remember “Web 2.0″?), the demand for richer, more interactive, client-side scripting increased. As that demand increased, a lot of us continued using JavaScript without really understanding the language (again, I’m guilty). I’ve learned a lot since then, in large part from working with Joe Eames (@josepheames) on our Html5 player, which we set out to do in a much more methodical and maintainable way. I learned a lot about JavaScript objects during that project thanks to Joe’s expertise.

You might wonder, why do I need objects in JavaScript? Well, for the exact same reason you need them in other OO languages. They weren’t so necessary back in the day when JavaScript consisted primarily of small snippits or functions to handle simple things like onclick events. But, now that client-side JavaScript is evolving more and more into mini-applications (or not so mini in a lot of cases) it is becoming increasingly important that we structure that code in a well-encapsulated, cohesive way enabling us to write SOLID and maintainable JavaScript code. If you are writing more than just a few lines of JavaScript code and are not creating objects to encapsulate and separate responsibilities, understanding and using objects may help you write cleaner code.

So, what is a JavaScript object?
First of all it’s helpful to know that JavaScript objects can behave much like an associative array or a set of key/value pairs (like a dictionary, map, or hash in other languages). For each pair, the key is the property/method name and the value is the property value or method definition. So, it’s not surprising to see that, given an object called myCat, you can create properties on it in a way that looks very much like a dictionary or map, like this:
    myCat["name"] = "Fluffy";
    myCat["color"] = "White";
That syntax, by the way, is functionally equivalent to this syntax:
    myCat.name = "Fluffy";
    myCat.color = "White";

Why the difference?
Good question. Really, there is rarely a need to do it using the dictionary-type syntax; but that way does allow you to create property names that do not adhere to JavaScript identifier naming restrictions. For example, I could create a property like this myCat["fur color"] even though “fur color” has a space in it. I cannot do that in the second example. This may be helpful in rare occasions where you may be creating objects out of data you don’t control (like a json data source, for example). But keep in mind, if you create them like that, you must also consume them like that (var color = myCat["fur color"]); Generally it’s a good idea to steer away from non-identifier friendly property names. It’s also much nicer to be able to use the more simple syntax above.

Wait a second, where did myCat come from anyhow?
The object myCat would have had to have been created prior to executing the statements above or you’d get an error indicating that myCat is undefined (which happens to be true, since I’m currently catless). So lets look at some ways to do that. The easiest way to create an object is like this:
    var myCat = {};
That creates a new object with no properties or methods. Ok, so that’s not technically true. It would be more accurate to say it doesn’t have any of it’s own properties or methods. That object does have some inherited methods since almost all objects eventually inherit from Object in JavaScript and so they inherit its methods.

Lets look at some other ways to create an object, and this time I’ll create some properties while I’m at it:
Example A
    var myCat = {name: "Fluffy", color: "White"};
Example B
    var myCat = Object.create(new Object());
    myCat.name = "Fluffy";
    myCat.color = "White";
Example C
    function Cat(name, color) {
        this.name = name;
        this.color = color;
    }
    var myCat = new Cat("Fluffy", "White");
All of these create an object with two properties (name and color) with their values set to “White” and “Fluffy”, respectively. So, why would you ever use Examples B and C if you can do this just as easily with the syntax in Example A? Well, if all you need is a cat with a name and a color, then there is no reason to use the other examples. In fact, I almost never use the syntax in Example B. But what if you want to be able to create multiple instances of an object that have the same shape and have some common properties initialized to the same values? That is where Example C comes in. The Cat function can be used to create multiple instances of a cat.

Why is Cat a function, shouldn’t it be a class?
That is a question OO programmers learning JavaScript have been asking for ages. JavaScript doesn’t have actual classes (although like it or not, it is about to). Rather than provide classes, JavaScript uses constructor functions and uses the new keyword to create instances of the type defined by the constructor (and it’s prototype). The Cat function is a constructor function. A constructor function, combined with the new keyword on the last line of that example, work together to create new objects in much in the same way classes do in other OO languages.

Prototype, what is that? And I still don’t see why I’d use Example C!
In the above paragraph, I stated that JavaScript uses constructor functions with the new keyword to create objects defined by the constructor (and it’s prototype). But I have yet to show you how to use the prototype of a constructor function — and for that reason, our examples have not been very interesting (why use Example C when you could use Example A).

Let's expand Example C some:
    function Cat(name, color) {
        this.name = name;
        this.color = color;
    }

    Cat.prototype.age = 0;

    var myCat1 = new Cat("Fluffy", "White");
    var myCat2 = new Cat("Scratchy", "Black");
Now both cats 1 and 2 have different names and colors, but they are both age 0. Of course, I can now change each of their ages individually, but it’s nice that I can already count on the fact that that property is there and initialized to 0. That prototype is used to define members that will be inherited by objects created by the Cat constructor function. Don’t worry too much about the details of prototypes for the sake of this post. I will be submitting a follow-up post to this one that covers prototypes much more in depth.

One subtle thing to take note of where JavaScript varies from other OO languages is in the inheritance of property values. If I were to set Cat.prototype.age = 3 in the example above, every cat created from the Cat function would have a default age of 3. This is a departure from what you may be used to with traditional properties in other languages.

Let’s make this example even more interesting by exploring how we would add a method to the Cat function:
    function Cat(name, color) {
        this.name = name;
        this.color = color;
    }

    Cat.prototype.age = 0;
    Cat.prototype.speak = function() {
        alert('meow');
    }

    var myCat1 = new Cat("Fluffy", "White");
    var myCat2 = new Cat("Scratchy", "Black");
Now our cats are no longer mute. We can call myCat.speak(); and it will alert the value “meow”. This makes our Cat constructor function much more interesting. You can see how using this structure you could create constructor functions that have all the power of classes in other languages (plus a few more features, like inheriting values).

Now lets tie up a few loose ends…

So are objects really just a dictionary?
Remember earlier I said that objects were not much more than dictionaries or a map? Well, they are more than just that (see “More information on properties” below) but just to illustrate that they do have dictionary-like behavior, look at the following code (which will work with any of the myCat objects from examples A, B, and C):
    for(var prop in myCat) {
        console.log(myCat[prop]);
    }
This code would produce the following in the console log:
  Fluffy
  White
Notice that we could iterate over the properties in an object and access each of them, just like you could with a dictionary.

So all objects inherit from Object?
When I introduced examples A, B, and C above, I stated, that “almost all objects eventually inherit from Object.” Most likely all the objects you’ll create will inherit from Object (or some other object that inherits from Object). It is unusual to do otherwise, but just to illustrate that idea out here is how you could do that:

Using Example B
Instead of doing var myCat = Object.create(new Object());, you could simply do this: var myCat = Object.create(null);.

Using Example C
After defining the function in Example C, you could simply do the following to make it so that none of the instances created from the constructor function inherit from object: Cat.prototype.__proto__ = null;. The __proto__ property will be explained further in my post on prototypes.

More information on properties
There is another way to make properties, and again the need for this is rare, but it is good to understand that there is more to properties than meets the eye. When we created properties on objects in all of our examples above, it was actually doing much more than you would think. They are all equivalent to doing the following
    Object.defineProperty(myCat, 'color', {
      value: "White",
      writable: true,
      enumerable: true,
      configurable: true
    });
Notice that all properties have three properties themselves that affect the way they behave. The writable property is fairly self-explanatory; if a property is not writable, it’s value cannot be changed in your script after it is created. The enumerable property affects whether the property shows up when enumerating all the properties using for(var prop in myCat). And finally, the configurable property specifies whether the property’s writable and enumerable properties can be changed once the property has been created. Again, the need for these is much less common.

Conclusion
This should give you enough information to start creating your own constructor functions and objects and using working in a way much more familiar if you are used to doing OO programming. If you would like to read a little more about objects, this post (which I also linked above) has some great information. That post is about the new classes coming in the next version of JavaScript, but he had some great examples illustrating objects using the current JavaScript functionality too.

No comments:

Post a Comment