Archive

Posts Tagged ‘rant’

How to write a “class” in JavaScript

February 16, 2013 2 comments

Of all the problems with JavaScript, by far the worst is the way people try to [ab]use it based on their training in Java and Object Orientation. They want to write a class, and then they want to write another class that inherits from the first class, because That How You Write A Software’sTM.

And this is made worse by a few features in the language that encourage people to make these mistakes: new, prototype, this.

The first hurdle to get over is inheritance. Consult any current text on OO and it will tell you that interface inheritance is fine, but implementation inheritance is a heap of trouble, so try to avoid it. In JavaScript, interface inheritance is completely unnecessary because there is no static typing at all. So that leaves implementation inheritance, which is what people usually mean by “inheritance” when they’ve been to Hollywood Upstairs Java College, and this is the part that even Java experts try to warn you away from.

So to get this absolutely clear: when you try to do inheritance in JavaScript, you’re trying to replicate something from Java that experts tell you not to do in Java. If you already know that, and you’re still insistent on trying, I can’t help you. You’re obviously insane.

For everyone else, this is the simplest way to write something that serves the approximate purpose of a “class” in JavaScript. It’s a factory function: when you call it, it manufactures an object:

var person = function(firstName, lastName) {
    return {
        getFullName: function() {
            return firstName + ' ' + lastName;
        },
        setFirstName: function(newName) {
            firstName = newName;
        },
        setLastName: function(newName) {
            lastName = newName;
        }
    };
};

// Usage:
var henryJones = person('Henry', 'Jones');

console.log(henryJones.getFullName());

henryJones.setFirstName('Indiana');

Note how there was no need to copy the constructor parameters into some separate fields. They already behave like private fields (actually more private than in the JVM or CLR, because in those runtimes you can use reflection to get to private fields).

There is a difference between the above pattern versus using prototype on a constructor function that has to be called with new – I mean aside from the convenience of not have to use new and this everywhere). The difference is that for each instance of a person, we create four objects: the one we return and the three functions stored in it.

If instead we did it the messy way:

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
};

Person.prototype.getFullName = function() {
    return this.firstName + ' ' + this.lastName;
};

Person.prototype.setFirstName = function(newName) {
    return this.firstName = newName;
}

Person.prototype.setLastName = function(newName) {
    return this.lastName = newName;
};

// Usage:
var henryJones = new Person('Henry', 'Jones');

Now the three function objects are shared between all instances, so only one object gets created per person instance. And to the typical, irrational programmer who places premature optimisation above all other concerns, this is vastly preferable. Never mind that the fields firstName and lastName are now public! That is a minor concern compared to the terrible thought of creating three extra function objects per instance.

But hang on one second: have you considered the following factors?

  • How many person instances are going to exist at any time while your code is running? If it’s small, then the cost per instance is irrelevant.
  • What other things are you going to do, per instance? If you’re going to build a load of DOM nodes with animations on them and fire off AJAX calls every second (come on, admit, that’s exactly what you’re going to do), then the cost of a few function objects per instance is lost in the noise.
  • Have you realised just how amazingly good modern JS runtimes are? They love allocating and throwing away lots of little objects.

If you do ever run into a situation where the difference is significant, you can apply the necessary optimisation in one place: inside your person factory function, without changing any other code:

var person = (function() {
    var vtable = {
        getFullName: function() {
            return this._firstName + ' ' + this._lastName;
        },
        setFirstName: function(newName) {    
            this._firstName = newName;
        },
        setLastName: function(newName) {
            this._lastName = newName;
        }
    };
    return function(firstName, lastName) {
        var p = Object.create(vtable);
        p._firstName = firstName;
        p._lastName = lastName;
        return p;
    };
})();

But in the vast majority of situations, there will never be any need to do that (i.e. when you do it, the difference will be practically unmeasurable), so why bother until you have a realistic model of your application with which to carry out your performance testing? (And you’ve also made sure that you have nothing better to do with your time…)

To find out precisely how much difference this makes, and thus when it might be worth worrying about it, all we have to do is try allocating some objects in the Chrome console. Let’s pick a ridiculously big number: a million.

var start = new Date().getTime();
var p = [];
for (var n = 0; n < 1000000; n++) {
    p.push(person('Henry', 'Jones' + n));
}
console.log(new Date().getTime() - start);

We can then run this test three times each with the plain and optimised implementations and find out what the overhead actually amounts to.

The first point of interest is that the allocation of a million objects takes about two and half seconds in a freshly-opened Chrome instance on my run-of-the-mill Dell notebook, with either implementation. Taking the average of three runs with each, the “fast” version took 2390 ms and the “slow” took 2570 ms. That’s 180 ms difference over a million allocations! We’re talking about mere nanoseconds per extra function object being allocated. Just forget about speed being an issue. It’s not.

What about memory? The relevant Chrome process showed an increase of 81 MB for the “fast” version, versus 204 MB for the “slow”, so a total overhead of 123 MB caused by those extra function objects. Again, that’s shared across a million objects, so the overhead per object is just 130 bytes or so. Is that a big deal?

If you’re writing a game and trying to model specks of dust floating around the screen, it might be realistic to allocate a million of something. But in real business apps, we don’t create a million person objects all at once. We create objects based on downloaded data that we retrieve in small pages from the server (otherwise if a thousand users try to open the app at the same time, the server will have transmit gigabytes of stuff that none of those users have enough lifetime left to ever read).

So a hundred objects is more realistic. Then the total overhead in this example would be 13 KB – the space consumed by a small image, the kind used in most real web apps without a moment’s thought.

Another comparison is to look at the overhead of DOM nodes. The objects we create in JavaScript are typically there to create and manage our user interface elements in the browser DOM. How much does a single empty DIV cost?

var p = []; 
for (var n = 0; n < 1000000; n++) {
    p.push(document.createElement('div')); 
}

That added 128 MB to a fresh Chrome process. What a coincidence! 134 bytes per empty DIV, a fraction more overhead than our three function objects.

So the conclusion must be: relax, stop worrying about illusory “performance” concerns, and focus instead on writing code that is readable, maintainable, hard to get wrong, hard to break accidentally when modifying it, and so on.

Categories: Uncategorized Tags: , ,