Pragmatic OOP in JavaScript Part 2 : To prototype or not to prototype

My recent post on object orientation in JavaScript has resulted in some quite interesting comments. Good fodder for a sequel.

Prototype

JavaScript is said to be a language with only objects and no classes. A JavaScript object is constructed using a constructor function. I showed two different ways to set up object members. The official way to do that is using the object's  prototype.

function ProtoAnimal(named)
{
    var name = named ? named : 'Unknown';
    this.Name = name;
}

function ProtoAnimal.prototype.MakeSound()
{
    return 'That\'s the ' + this.Name + ' sound' ;
}

The problem with functions being part of the prototype is that they can only share data members which are part of the object's public interface, that is this. There is no way to encapsulate data or method members. As an alternative approach I showed a way to introduce members in the constructor function. These members can share private encapsulated data.

function Animal(named)
{
    var name = named ? named : 'Unknown';

    this.Name = function()
    {
        return name;
    }

    this.MakeSound = function()
    {
        return 'That\'s the ' + name + ' sound' ;
    }
}

Now the name member itself is encapsulated and cannot be changed by a consumer of the object; only read using this.Name().

The upside of the prototype approach is that all objects instantiated by the same constructor functions share the implementation of the prototype members. This is demonstrated by this code

function ProtoAnimal.prototype.MakeSound()
{
    return 'A PROTO BASE sound';
}

function ChangedSound()
{
    return 'A CHANGED sound';
}

The consuming code instantiates two ProtoAnimal objects and then sets the prototype's method to the ChangeSound function. (See pt1 how to change method members in JavaScript)

var myAnimal = new ProtoAnimal('Cat');
var myAnimal2 = new ProtoAnimal('Dog');

ProtoAnimal.prototype.MakeSound = ChangedSound;

text1.value = myAnimal.MakeSound();
text2.value = myAnimal2.MakeSound();

You will see that MakeSound on both objects returns the result of the ChangedSound function. To achieve the same result with the encapsulated implementation you have to change the method member of every instantiated object

var myAnimal = new Animal('Cat');
var myAnimal2 = new Animal('Dog');

myAnimal.MakeSound = ChangedSound;
myAnimal2.MakeSound = ChangedSound;

text1.value = myAnimal.MakeSound();
text2.value = myAnimal2.MakeSound();

So the object's prototype does provide some class-like functionality which can be useful when inheriting. But the downside is still the lack of encapsulation.

What's the cost ?

As objects share implementation in their prototype it is most likely that using the prototype would result in far more efficient code. To get an idea of the magnitude I set up a little test. Running script straight from the Windows explorer (Double click a .js file) starts the Windows scripting host. The process is visible in the Windows task manager. You can get an idea of the amount of memory the script is consuming.

At first sight every non-prototype object has a copy of the MakeSound method; that could get huge. To simulate some real code in there I gave it this (nonsense) implementation.

function Animal(named)
{
    var name = named ? named : 'Unknown';

    this.Name = function()
    {
        return name;
    }

    this.MakeSound = function ()
    {
        var moreWaste;
        var moreWaste2;

        /* This is just some nonesense code */
        for (var i= 0; i < this.Name.length; i++)
        {
            var wasted = this.Name.charAt(i);
            if (wasted == 'a')
            {
                moreWaste = Math.exp(i);
            }
`        }
        for (var j= 10; j > this.Name.length; j–)
        {
            var wasted2 = this.Name.charAt(j);
            if (wasted == 'z')
            {
                moreWaste2 = i + Math.Random;
            }
        }
        /* End of nonesense code
                just another comment */

        return 'That\'s the ' + name + ' sound\n' + moreWaste + '\n' + moreWaste2;
    }
}

The ProtoAnimal's MakeSound method got the same treatment.

The script to test memory usage and constructing speed

var WSHShell = WScript.CreateObject("WScript.Shell");
var zooSize = 200;
var zoo = new Array();

WSHShell.Popup('Ready ?');

var start = new Date();
for (var i = 0; i < zooSize; i++)
{
    zoo[i] = new ProtoAnimal("Alive " + i);
}

var constructTime = new Date() – start;
WSHShell.Popup('No of objects ' + (zooSize) + '\nTime to build ' + constructTime + ' ms');

The popup's provide the moment to check memory usage. I ran the test for 2 to 200000 objects. It is a wet finger (as the Dutch saying goes) but nevertheless some results.

  • It takes creating 2000 objects to get some real results (the curve pf object count vs memory becomes straight)
  • It just takes one second (P4 2.6 Ghz) to construct over 30000 objects
  • The non-prototype objects take 50% longer to construct
  • Creating 200000 animals costs 300 Mb, 200000 proto-animals still cost 150 Mb.
  • Using prototype objects takes half the amount of memory. Also at lower numbers. This was found to be linear in the range of 2000 objects or more.

Especially the linear behavior gives the impression that a prototype is not as simple as it looks. Overall the tests show that it is more efficient to use the prototype but the results (imho) no way prohibits working the encapsulated way. It is a balance between better code and performance metrics. These days the latter is, thank goodness, not always the leading. You don't have to chose between the two. Only a member which needs private members has to be part of the constructor function, all others can be part of the prototype.

Inheritance revisited

This is real inheritance as seen in C#

    class MyBaseClass

    {

        internal virtual bool MyFunction(int i)

        {

            return i > 0;

        }

    }

 

    class MyInheritingClass : MyBaseClass

    {

        internal override bool MyFunction(int i)

        {

            bool isOk = base.MyFunction(i);

            if (isOk)

            {

                isOk = i > 10;

            }

            return isOk;

        }

    }

The inheriting class overrides MyFunction. When implementing the implementation in the baseclass is still available.

JavaScript simulates inheritance. We have seen it is no problem at all to assign a new implementation to a function. But that will overwrite the original implementation, there is no such thing as base.MyFunction. What most of JavaScript's inheritance implementations do is copy the members of the prototype from the base to the inheriting class. Libraries such as discussed here have almost all inheritance features as found in a true OOP language available. Doing so they introduce ways of coding which may raise an eyebrow when you're used to C#. And still they bypass encapsulation.

Looking at the core of the problem there is only one thing which counts: the constructor function returns an object; that object has members. Which members there are depends on the prototype of the constructor function and the constructor function itself. In my previous post I showed a constructor which republished the members of an aggregated base object.

function Cat()
{
    var base = new Animal('Cat');

    this.Name = base.Name;
    this.MakeSound = function()
    {
        alert('Mew');
        return base.MakeSound();
    }
}

I would like to add an alternative implementation, a variation on one suggested by Jeremy

function Cat()
{
    var base = new Animal('Cat');
    var MakeSound2 = base.MakeSound;

    base.MakeSound = function()
    {
        alert('Mew');
        return MakeSound2();
    }

    return base;
}

The main difference is that this constructor function explicitly returns the aggregated base object. By default a constructor implicitly returns this. This constructor also shows that JavaScript does not understand two implementations of a member, the base one and the overridden one. The base MakeSound method has to be copied to MakeSound2, else it would be lost when creating the overridden implementation. Using this way to simulate inheritance I don't have to re-publish all members of the base class but I end with having to store the base implementation myself.

To conclude

I don't want to jump to conclusions; I can only advise you to be pragmatic. The main problem with JavaScript is that it can so much. With lots and lots of possibilities to really screw up your code or create something nobody will understand. Not even yourself when revisiting your own creation over time. I do hope to have you provided with enough possibilities to satisfy both your performance and clarity-of-code needs. 

This entry was posted in ASP.NET, Coding. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Matt

    You should have a look at this https://github.com/TremayneChrist/ProtectJS It allows you to privatise your methods on the prototype chain!

  • Kevin Prichard

    Interesting article, Peter.

    Choosing fully-exposed prototype style versus the more expensive encapsulated style shouldn’t be a black and white proposition. Use encapsulation where you need to ensure private data remains encapsulated (and other devs have to use your privileged public methods), and use .prototype for commodity objects.

    It would be worthwhile to see the same test run on IE, FF, Saf, to see if the cost is proportionally similar on those JS platforms as well.

    > I don’t think encapsulation necessarily has to mean “inaccessible”

    Encapsulation in IS means “information hiding” – That’s a key feature of OOP and OO languages, the ability to hide information so that you and other developers must use the interface of *public* methods presented by the class or constructor function. In Javascript, I use it when I really don’t want other developers to modify internal state of an object directly. For commodity objects, like representations of database rows, I care less and use prototype and this.property notation.

    http://en.wikipedia.org/wiki/Information_hiding

  • http://codebetter.com/blogs/peter.van.ooijen/ pvanooijen

    Beside the function with the code there is the the object this itself. This carries all (public) values. Included those not in the code.

    Imho JS is not oriented at all. You can program all kind of different ways using it. But never (?) as good as you can in an other language which is really x-oriented.

  • sergiopereira

    I like to think of JS as an object oriented language where all the classes inherit from the Function class, even the Object class. That may not be entirely true but would explain why the implementation of the concrete objects is called ‘prototype’.
    Maybe JS is just a Function-oriented language then :)

  • http://codebetter.com/blogs/peter.van.ooijen/default.aspx pvanooijen

    JavaScript just lacks the class to be fully object oriented :)

    To me encapsulation should include hiding everything which does not have any meaning to the consumer of the object. Be it private instance vars or be it helper methods. To achieve that in JS you need closures. I’m not saying it is the same, it leads to the same result. It’s pragmatic, not CS

    My test is “a wet finger” which is worse than an educated guess. It tries to compare the cost of the two techniques. On one platform, without any DOM. The only thing the test does show is that the _relative_ differences are not big (1.5 times the speed twice the memory). I agree it would have been better to run one in an explorer DOM as that is more the usual ecosystem JS lives in.

  • http://substantiality.net Sam

    > “JavaScript is said to be a language with only objects and no classes.”

    Yes! That’s just _so_ right.

    > “There is no way to encapsulate data or method members.”

    I don’t think encapsulation necessarily has to mean “inaccessible”. A complex method is encapsulated regardless of the accessibility of some member variables in an instance IMO. You’re giving a humane interface to a complex set of behaviour. That’s all encapsulation should have to mean I think.

    Besides, what you’re demonstrating in your first example is actual JavaScript’s Closure support. It’s nothing like private accessibility in .NET.

    The only real criticism I have of your testing methodology is using WScript and arbitrary object counts. You’re not even hinting at what might be involved in DOM manipulation, or it’s cross-browser limitations (you can’t extend the prototype of IE DOM elements since they’re all generic Objects). The benchmarks are pretty misleading too I think. You only tested one of the half dozen or so JavaScript interpreters most commonly used, and you imply that 1 second isn’t a long time to perform a simple operation not even involving DOM manipulation. 1 second is a pretty long time on a fast computer like ths. Especially since it’s not touching the DOM, which will multiply the number of functions called dramatically.

    The main difference between your two methods is that in one, you’re creating Objects, in the other, you’re creating Function instances. It might appear similar, as if they both fall under the umbrella of object creation/instantiation, but the ramifications run deep.