“Virtual” Functions in JavaScript

How-to

Alice

Hey BlackBerry® platform devs! Please welcome a guest blogger today – Laurent, who’s pumped to discuss JavaScript® and its myriad functions in developing your latest and greatest BlackBerry app. Take it away, Laurent! – Ed.

I love JavaScript. Yeah, you heard me right. I remember being part of a groundbreaking project back in 2002 where I was thrown into the world of JavaScript and was given the mission to go crazy with it. Several hundred thousand lines and a fully functional in-memory SQL engine later, I gained huge respect for the language that many of my co-workers saw only as a toy language for browsers. Oh, how far we have gone since then. To me, JavaScript is kind of like SmallTalk with a C syntax, and the expressiveness and power of the language is impressive.

One of my favorite patterns I use all the time is how to implement “virtual” functions in a simple fashion. There are heavier patterns such as mix-ins and frameworks to do all of it, but honestly, when you are building a quick app that maxes out at only a few thousand lines of code, sometimes I think that it’s just simpler to go for hand-to-hand combat.

We have used this technique heavily in Alice, a new micro-library focusing on fancy visual effects using CSS3 2D and 3D facilities. We created reusable parts in a “base class”, and implemented virtual functions in a “derived class,” where logic that is specific to a particular effect is implemented. I use quotes everywhere here because as you’ll see, the traditional Object Oriented notions of Classes, Base and Derived are somewhat fuzzy with JavaScript.

So what am I talking about? Let’s say for example that you implement a piece of logic where you need to collect DIVs nested in a given DIV. You may write very simple code such as:

function getSubDIVs(nodeId) {
    var node = document.getElementById(nodeId);
    var DIVs = [];
    for (var i = 0; i < node.childNodes.length; i++) {
        var e = node.childNodes[i];
        if (e.nodeName.toLowerCase() == "div")
           DIVs.push(e);
    }
   return DIVs;
 }

For those unfamiliar with JavaScript, DIVs is declared as an array, and “push” is a method to add a new element at the end of that array. The rest should be pretty self-explanatory. Overall, this code is simple and straightforward. But what if tomorrow, you need to do other things with the sub-DIVs? You need to abstract some of the logic. For example:

function getSubDIVs(nodeId) {
    var node = document.getElementById(nodeId);
    var DIVs = [];
    for (var i = 0; i < node.childNodes.length; i++) {
        var e = node.childNodes[i];
        if (doSomething(e) == true)
         DIVs.push(e);
    }
   return DIVs;
 }

JavaScript is a wonderful language, and there are two aspects in particular that makes it extra special:

  • Every value is an Object.
  • Functions are like any other values.
  • Functions run within a dynamic context (what this refers to).

The second property provides us with one solution already. Functions in JavaScript are like any other object, so you can just pass one in as any other parameter.

function getSubDIVs(nodeId, doSomething) {
    var node = document.getElementById(nodeId);
    var DIVs = [];
    for (var i = 0; i < node.childNodes.length; i++) {
        var e = node.childNodes[i];
        if (doSomething(e) == true)
         DIVs.push(e);
    }
   return DIVs;
 }

Note that doSomething is now a parameter, so I can pass in any function I want in there, dynamically:

function filterOnlyDIVs(e) {
    return (e.nodeName.toLowerCase() == "div");
 }
getSubDIVs("myNodeId", filterOnlyDIVs);

JavaScript even support anonymous inline functions, which allows you to write even simpler code:

getSubDIVs("myNodeId",
           function(e) { return e.nodeName.toLowerCase() == "div"); }
          );

You can inline the function definition itself, and pass it in as a parameter. Neat, right? That is cool, but there is something missing. What if you are dealing with objects? What if your function needs to have access to some state or information? For example, let’s assume the following JavaScript object definition:

function MyObject {
    this.count=0;
    this.filterOnlyDIVs = function(e) {
       if (e.nodeName.toLowerCase() == "div") {
    	   ++this.count;
    	   return true;
    	 }
       return false;
    }
 }

Now you have a problem. A function is a value in and of itself, but there are tricks in terms of defining which context it actually runs in, that is, what does this mean? If I repeat my previous code, then I’ll have quite a surprise.

var Obj = new MyObject();
getSubDIVs("myNodeId", MyObject.filterOnlyDIVs);
alert(Obj.count);

Trick question: what will be printed out? No matter what, you’ll get 0 because several pernicious things are happening under the covers:

  • We are passing in a pointer to the function, in and of itself, without any context of any object.
  • When the code ++this.count; runs, it runs out of the function itself; remember, functions are objects too.
  • When you print the value of count, you are then using your object instance, and the value is 0 no matter what. You implicitly defined dynamically a variable called count for the function itself, so if you did alert(getSubDIVs.count), that’s where you’d see the value… Or would you? Maybe it’s MyObject.filterOnlyDIVs.count? Play around, and see for yourself. Hint: functions are Objects too.

JavaScript can be tricky that way, and that’s the way it works. But we can use it to our advantage with an interesting pattern. We only have to do one very simple, but major, thing:

function getSubDIVs(nodeId) {
    var node = document.getElementById(nodeId);
    var DIVs = [];
    for (var i = 0; i < node.childNodes.length; i++) {
        var e = node.childNodes[i];
        if (this.doSomething(e) == true)
         DIVs.push(e);
    }
   return DIVs;
 }

All we have done now is to say that doSomething is no longer passed in as a parameter. Instead, it will be executed in the context of this. So, the more astute amongst you will ask “But what is this? I thought the function was itself an Object, and that’s what this referred to?” Beautiful question! If you just call the method, you will get an error, because there is no doSomething function out of this.

getSubDIVs(nodeId);

So this is the time I am asking you to think about virtual functions in traditional Object Oriented languages. What would you do here? You’d define doSomething as a method, and override it in a child class. You can’t do that per se in JavaScript, but you can do something very cool: you can attach that function dynamically to any object you have.

function MyObject {
    this.count=0;
    this.doSomething = function(e) {
       if (e.nodeName.toLowerCase() == "div") {
    	   ++this.count;
    	   return true;
    	 }
       return false;
    }
   this.getSubDIVs = getSubDIVs;
 }

This is so beautiful and elegant – you are effectively assembling your object on the fly out of parts. You declare a new method for MyObject (they can have the same name, but it’s not necessary) and assign to it the “base method” getSubDIVs (which is really a global function). Now, when you make your call, and when getSubDIVs executes, it now does so in the context of your object (where this.count is meaningful), and everything in the world is good.

var Obj = new MyObject();
Obj.getSubDIVs("myNodeId");
alert(Obj.count);

This is what goes on:

  • You create an instance of MyObject called Obj.
  • You call the MyObject.getSubDIVs method on Obj.
  • This in fact calls the global function getSubDIVs.
  • This happens without any level of indirection or performance penalty since the MyObject.getSubDIVs “variable” is in effect the global function getSubDIVs.
  • In the global function getSubDIVs, this is actually Obj, so ++this.count; works your Obj.
  • When you then print out Obj.count, you get the right count.

And voila! You have pieced together, manually, the core tenets of Object Oriented development and encapsulated reusable code, which you customize with “derived” functionality. You have encapsulated a piece of logic somewhere (here, in a global function). Then in that function, you make a call to a “virtual function,” which you then implement separately in a class. You tie it all together by attaching the base method to that class.

You can use frameworks to do this, but honestly, I think that it’s so simple that, unless you work on very large projects, you are better off with direct hand-to-hand combat. JavaScript allows you to write well encapsulated code, so go ahead and do it!

Join the conversation

Show comments Hide comments
+ -
blog comments powered by Disqus