Object Oriented programming in JavaScript PART4

Pattern 5: “Crockford” Patterns

Continued from part 3

However, there’s something to be said for having constructor functions. Having a constructor function create an object for us gives to that encapsulation we want and makes the process easier; we don’t have to worry about wiring up the right things; it just works.

Along these lines, we’re going to discuss two patterns that Douglas Crockford talks about in his presentation Function the Ultimate, which is part of his series, Crockford on JavaScript.

  • The new_constructor function
  • The Power Constructor pattern

Let’s start with the new_constructor function. This might remind you of the classical patterns we discussed earlier. However, they are only similar in that the calling of them; what’s going on behind the scenes is very different. With the classical models, the libraries we were using were going to great lengths to make JavaScript appear classical. Here, we’re just wrapping up some easy functionality.

Take a look at the Crockford’s new_constructor function:

function new_constructor (extend, initializer, methods) {
    var func, prototype = Object.create(extend && extend.prototype), key;

    if (methods) {
        for ( key in methods) {
            if (methods.hasOwnProperty(key)) {
                prototype[key] = methods[key];
            }
        }
    }
    func = function () {
        var that = Object.create(prototype);
        if (typeof initializer === 'function') {
            initializer.apply(that, Array.prototype.slice.call(arguments));
        }
        return that;
    }
    func.prototype = prototype;
    prototype.constructor = func;
    return func;
}

If you spend a few minutes looking at it, it doesn’t look so daunting. It takes three arguments: the first is the object we want to use as a prototype base; think of this as the superclass, or parent class. The second parameter is the constructor function; this on the new object when we create it, to give it its own properties. Finally, we have an object called methods; each method will be added to the prototype of the object we’re creating.

If you follow through the function, you’ll see that we create a new object that has the prototype of the old object as its prototype. Then, we add the methods for this object type to that prototype. Finally, we create a function that will create a new object based on our new prototype, and run our constructor function on it; this is where your knowledge of apply will come in handy. Finally, we do some housekeeping and make sure the prototype is pointing in the right direction. Then we return the function.

The result is a function that we can pass the parameters of the constructor function to. Let’s see this in practice.

var person = new_constructor(Object, function (first, last, age) {
    this.first_name = first;
    this.last_name = last;
    this._age = age;
    this.interests = [];
},
{
    age : function (age) {
        if (age) {
            this._age = age;
            return age - 5;
        }
        return this._age - 5;
    },
    add_interests : function () {
        for (var i = 0; i < arguments.length; i++) {
            this.interests.push(arguments[i]);
        }
    }
});

Our first parameter is Object, which means we aren’t inheriting from anything other than the built-in Object. Second, we have our initializer function, which you’ve seen many times before. Lastly, we have our methods object, which has the two methods all our people have had.

var bob = person("Bob", "Bills", 40);
console.log(bob);

You’ll see that we have a person, just like we’d expect.

Inheriting from person isn’t too hard; we just pass person as the first parameter.

var superHuman = new_constructor(person, function (first, last, power) {
    this.first_name = first;
    this.last_name = last;
    this.power = power;
    this.interests = [];
    this.add_interests(power);
},
{
    wield_power : function () {
        return this.power.toUpperCase() + " to the rescue!!1!!1";
    }
});

var bill = superHuman("Bill", "Smith", "thunder");
console.log(bill);

You might be wondering why I’m not capitalizing person for superHuman; it’s because that convention mean we should be using the newkeyword, which isn’t the case.

So, how about a real-web example of new_constructor usage? How’s this?

var tooltip= new_constructor(Object, function (btn, panel) {
    this.button = jQuery(btn);
    this.panel  = jQuery(panel);
},
{
    show : function () { this.panel.show(); },
    hide : function () { this.panel.hide(); },
    bind_events: function () {
        var that = this;
        this.button.bind('mouseover.tooltip', function () { that.show.call(that); })
                   .bind('mouseout.tooltip',  function () { that.hide.call(that); });
    },
    unbind_events : function () {
        this.button.unbind(".tooltip");
    },
});

var t = tooltip("#button", "#panel");

t.bind_events();

It’s just what you would expect. And inheriting:

var fading_tooltip= new_constructor(tooltip, function (btn, panel) {
    this.button = jQuery(btn);
    this.panel  = jQuery(panel);
},
{
    show : function () { this.panel.fadeIn(); },
    hide : function () { this.panel.fadeOut(); }
});

var ft = fading_tooltip("#button", "#panel");

t.unbind_events();
ft.bind_events();

We just pass tooltip as the first parameter, and we can inherit the event binding functionality.

However, you may notice that we’re back rewriting identical constructor functions; too bad we can’t inherit those. However, let’s look at Crockford’s idea of the Power Constructor, which is pretty simple, and does support—in a way—inherited constructors.

A Power Constructor is a function that performs four steps:

  1. Create an object — This could be done in any of the ways we’ve discussed so far: object literal, module, Object.create … even calling another power constructor.
  2. Make any private variables or functions — These will be accessible to the object via closure.
  3. Augment the object with funtionality that makes use of your private varibles and functions.
  4. Return the object.

It’s important to note that these four steps won’t always appear in this order; also, simple objects might not always have private functions to hide, so you might not always have all four steps.

I actually like this method best (hence, I’ve saved it for last). I like it because it’s just a normal function; you aren’t calling another function to create a function; it’s easy to read, easy to write, and works perfectly.

So, let’s see how this plays out for our people.

function make_person (first, last, age) {
    function set_get_age (new_age) {
        if (new_age) {
            age = new_age;
        }
        return age - 5;
    }

    return {
        first_name : first,
        last_name : last,
        age      : set_get_age,
        interests : [],
        add_interests : function () {
            for (var i = 0; i < arguments.length; i++) {
                this.interests.push(arguments[i]);
            }
            return this.interests;
        }
    };
}

This is our make_person power constructor. Since we aren’t inheriting this object from anywhere, there’s no need to create the object before we return it; therefore, I’m just returning an object literal at the end. Step 2 is private functions, and we’ve got one of those; ourget_set_age. We’re also doing something rather cool; we need a private variable to keep track of our real age. We could add one, but we’re accepting an age as a parameter, so we can just use that parameter as the private age variable. From inside our function, we’ll just reset the value of that parameter when we’re given a new age. Then, we can reference set_get_age from our age property in the object we’re returning. Oh, and because what we’re returning is a real “instance” of the object, and not a prototype, we don’t have to worry about having reference values like our array interests on it.

var fred = make_person("Fred", "Flintstone", 50);
console.log(fred);

As you’ll see, our make_person constructor is working just fine.

So how do we do inheritance with power constructors? That’s all part of Step 1 – creating the object.

function make_superhuman(first, last, power) {
    var person = make_person(first, last);

    person.power = power;
    person.add_interests(power);
    person.wield_power = function () {
        personurn this.power.toUpperCase() + " to the rescue!!1!!1!";
    }
    return person;
}

As you can see, this first thing we’re doing is calling make_person. This is how we can inherit constructors … and everything else too! Essentially, each superHuman is a person before getting the extra functionality.

var barney = make_superhuman("Barney", "Rubble", "Driving a Dinosaur");
console.dir(barney);

And this works just fine.

Tooltip time again!

function make_tooltip(button, panel) {
    return {
        button: jQuery(button),
        panel : jQuery(panel),
        show  : function () { this.panel.show(); },
        hide  : function () { this.panel.hide(); },

        bind_events : function () {
            var that = this;
            this.button.bind("mouseover.tooltip", function (e) { that.show.call(that, e); })
                        .bind("mouseout.tooltip", function (e) { that.hide.call(that, e); });
        },

        unbind_events : function () {
            this.button.unbind(".tooltip");
        }
    }
}

There’s nothing hiding here; we’re just immediately returning an new object literal, with very familiar properties. To inherit, things don’t get much harder:

function make_fading_tooltip(button, panel) {
    var person = make_tooltip(button, panel);

    person.show = function () { this.panel.stop().fadeIn(); };
    person.hide = function () { this.panel.stop().fadeOut(); };

    return person;
}

There’s something you might notice about this method; we’re not inherently dealing with prototypes here. Each time a power constructor is called, we create a new object from scratch. It’s true that our call to whatever is creating the object in Step 1 may be using a prototype to do so, but that’s not required. Then, when we add the functionality for this object, we don’t put it on a prototype; function are added directly to the object, and not shared among objects from the same constructor. If you’re going to be creating many copies of an object, it might be best to create a prototype outside the power constructor and use Object.create to make the objects.

var prototype_for_tooltip = {
    show : function () { ... }
    ...
};

function make_tooltip (btn, panel) {
    var tooltip = Object.create(prototype_for_tooltip);
    ...
}

Of course, if you’re worried about your prototype being tampered with, just wrap this all in a closure:

var make_tooltip = (function () {
    var prototype_for_tooltip = {
        show : function () { ... }
        ...
    };

    return function (btn, panel) {
        var tooltip = Object.create(prototype_for_tooltip);
        ...
    }
}());

That’ll keep things safe and optimized! Take Care :)

Loading Facebook Comments ...

One thought on “Object Oriented programming in JavaScript PART4”

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>