Skip to content

Matryoshka, you $.russianDolls

So as I have been rewriting my code, I found that I wanted to add more and more behavior on top of each other. For example, as I create symbols, I want to have some of them process a value on startup. Or I want to have checking behavior added to the usual processing. So without a prior knowledge as to what I need and how it will fit, and trying to avoid a cascade of if statements, how do we do this?

Key to our success is the Function method .apply. What it does is allow us to call a function, give the “this” context, and an array of arguments. (.call passes the second, third, … arguments as arguments). So if we have function fun, then fun.apply(cool, [3, "two"]) is essentially the same as cool.fun(3, “two”) That is, it looks to fun like it is a method of the object cool hence this = cool, and the arguments passed are [3, "two"]

So let’s say we have an object obj with function behave. But we want to add new behavior to behave. We still want the old behavior, but we also want the new. So we can do the following:

obj = {behave: function () {console.log("Please type");}}
obj.behave ();  //log: "Please type"
var newBehave = function () {console.log("Here's Johnny");}
var oldBehave =  obj.behave;
obj.behave = function () {
  oldBehave.apply(this, arguments);
  newBehave.apply(this, arguments);
}
obj.behave();  //log: "Please type",  log:"Here's Johnny"

Now when we call obj.behave, first oldBehave gets called with the arguments passed to obj.behave and with the this context being obj (unless obj.behave is called differently; in any event, whatever its this context is passed along), and then newBehave gets called also with the this and arguments being passed along.

By the way, arguments is a special array-like object which contains all passed arguments. Iterating over it is how we can deal with variable argument functions. It also has this nifty feature we are using here of just being able to pass along the arguments.

Also notice the use of a closure. I need to use oldBehave because if I called obj.behave in that function, it looks up obj.behave at the time of running in that scope. So presumably it finds itself and calls itself. This leads to the original never being called and, more annoyingly, an infinite recursion, I believe. Ouch. But by using oldBehave, we store the old function in a closure which will always be around to the new function and not lead to any recursion.

Okay, now we can write a little plugin that takes care of the details for us. Largely, we want one for the usual ideals of clarity, tedious error-checking, and the feeling of accomplishing something without doing so (building code to help us build code that helps us build programs to help us parse our documents to help us make pages that get interpreted in …)

$.russianDolls = function (obj, property, fun, funfirst) {
        var oldfun; 
        if (obj && typeof property === "string" && typeof fun === "function") {
            oldfun = obj[property];
            if (typeof oldfun === "function") {
                if (funfirst) { //old fun goes last
                    obj[property] = function () {
                        fun.apply(this, arguments);
                        oldfun.apply(this, arguments);
                    };
                } else { //old fun goes first
                    obj[property] = function () {
                        oldfun.apply(this, arguments);
                        fun.apply(this, arguments);
                    };
                }
            } else { //no old fun so new property
                //if (oldfun) {$.log("$.russianDolls: old property not a function!", arguments, oldfun)}
                obj[property] = fun;
            }
        } else {
            //$.log("russianDolls: some argument is not right type: ", obj, property, fun, funfirst);
        }
};

So we test the first that nothing is going to give us an error; if it does we quit and do nothing. This has the benefit that if we pass an empty function (such as when passing in a parameter from another function call that is not always used), we add nothing to our object. Then we save the old function into oldfun. If oldfun exists and is a function, we decide whether to use it first (default, else part) or second, and then we assemble the new behavior into a separate function. Note that we do not want to put any of these ifs in the new function otherwise it needs to evaluate them every time we call the new function. With this ordering, the code is told once what needs to be done and can then proceed without checking conditions each time. If no oldfun exists or it is not a function, then we create a new function and store it in that property. Note that one might decide on other behavior for when oldfun is not a function, in particular, issuing a warning such as the commented out line.

Here is a sample test:

        var a = {hi:"Waiting", addText:function(n){this.hi = this.hi + "Original"+n;}}; 
        $.russianDolls(a, "addText", function(n,s){this.hi = this.hi + "After"+n+s;});
        $.russianDolls(a, "addText", function(){this.hi =  this.hi + "Before";}, true);  //new function goes first
        a.addText("n","s"); 
        $.russianDolls(a, "whoAmI", function(){this.hi = this.hi+"New";}); //adding a new function
        a.whoAmI();
        if (a.hi === "WaitingBeforeOriginalnAfternsNew") {rejoice();}

Notice how the parameters get passed even if they are not used.

One use for this is I find that I sometimes iterate over an empty object for $.each when I am debugging. This throws an error but I have no idea where in the code it is. So one could add

if (debug) {$.russianDolls($, "each", function(obj, fun) {if (!(obj)) {console.log("each not given object", obj, fun)} }, true)}

obj is useless of course (undefined), but fun should be there and clicking it in firebug leads you to the code where the function is defined.

One could also have another plugin which could capture the error for misbehaving functions. I think this might be dangerous so I will not implement it quite yet.

One thing I have been thinking about is that the plugins I am writing encapsulate useful ideas. By naming them, by abstracting the behavior out to a single name, I can then express it with clarity and precision. And this is really why I want to learn parsing techniques (the refactoring is coming along nicely). Once one can define the expression of an idea in language, it becomes easy to say it, to use it, to think it. And with that is power. Give me your name, and I will have power over you.

P.S. Happy Halloween! This seems an appropriate post–a doppelganger creator function.

Post a Comment

Your email is never published nor shared. Required fields are marked *