Blog of Jeff A blog about programming and random other things.

25Aug/09link

“Brief” Guide to JavaScript – Part 8

Finally, another part. If you haven’t already, check out the previous parts of this series:

Advanced Topics

Finally, I can stop pushing off things I want to talk about. :) Here’s where I’ll talk about them.

Apply This Call (ordered for pun)

Now we’ll demystify the this variable. This is to represent the object that "owns" the function being called. This is easily shown by an event handler example:

// yes, the "bad" way, we'll talk why later.
document.getElementById('mylink').onclick = function(){
    alert(this.innerHTML);
};

Here we get the innerHTML of the element. For events, this is target/srcElement. This is preferred for its simplicity. Using this, we can also do something like:

function myhandler(){
    this.style.color = 'red';
}
for(var el : document.getElementsByTagName('p')){
    el.addEventListener('click', myhandler, false);
}

This will make the text of any p element red when the user click on it. This saves us time from having to write functions for each element.

Of course there's a caveat, IE's attachEvent sets this to the window object, making the above code useless in IE (even if you converted addEventListener appropriately). Your wrapper for adding events can change this with the next two handy functions: apply and call.

Apply and call executes a function with a provided this object and set of parameters. The only difference between the two is that apply accepts an array as the parameter list instead of call, which you pass parameters in directly.

function customFn(param1){
    alert('this = ' + this);
    aert('param1 = ' + param1);
}
// all three calls are identical
customFn('hello'); // always implies this = window object
customFn.call(window, 'hello');
customFn.apply(window, ['hello']);

This is handy when you need to assign a bind a different value to this for a function. In a way, this allows you to provide custom scoping to a particular function. Class-styled JavaScript frameworks use this to refer to the class (for methods).

Using the above myhandler function, we can pass a custom this, and the function wouldn't care that it wasn't called via an event:

myhandler.call(document.getElementById('my_element'));

Optional parameters

Undefined Parameters

This isn't quite new, but due to the nature of || and && (returning the truthy statement), we can use it as a rudimentary optional parameter feature:

function myfn(optional){
    var param = optional || 'my default';
    // use param (hopefully more useful than this) 
    return param;
}
// examples 
myfn(); // optional variable is undefined; returns 'mydefault' 
myfn('hello'); // optional variable is 'hello'; returns 'hello' 
myfn(false); // optional variable is false; returns 'mydefault'

The reason why this works is because any parameters not filled are automatically set to undefined, which is falsy to ||. The caveat about this method if we expect a boolean value. A false value will fill a default, so we have to check for undefined explicitly. This is only necessary if we want to accept boolean or integer values.

function myfn(optional){
    var param;
    if(optional === undefined){
        param = 'default';
    } else {
        param = optional;
    }
    // commented line below is same as above code;
    // var param = (optional === undefined) ? 'default' : optional;
    return param;
}
// examples 
myfn(); // returns 'default' 
myfn('hello'); // returns 'hello' 
myfn(false); // returns false
Variable Parameter Length

Sometimes we want to have a variable number of parameters, we can build a rudimentary clone of sprintf, or python's % operator on strings (changed to format in python 3). We'll only accept %s to represent a variable to replace with and a variable number of parameters which represents the values to replace it with.

To do this, we can use the special arguments array variable provided to functions which provides meta information about the specific function call. Each element in the array represents a parameter.

function sprintf(){
    var str = arguments[0]; // our string
    for(var i=1; i<arguments.length; i++){
        // replaces first occurrence of %s to the given parameter
        str = str.replace('%s', arguments[i]);
    }
    return str;
}
// usage: 
sprintf("hello %s", "john"); // returns "hello john" 
// returns "the sum of 1 and 2 is 3"
sprintf("the sum of %s and %s is %s", 1, 2, 1+2);

Besides parameters, we've used arguments.callee to refer to the current function being executed, which is handy for anonymous function recursion.

// recursive anonymous function that displays a predefined countdown using alerts. 
var num = 3;
(function(){ // count down     alert(num);     num—;
    if(num < 1){ return; }     setTimeout(arguments.callee, 0);
})();
Object-based Optional Parameters

Both optional parameter methods we listed so far have their drawbacks if you have multiple optional parameters. Say we have this theoretical function:

function createRobot(unique_id, name, greeting_message, goodbye_message){
    // unique_id is required, rest are optional parameters 
    var name = name || 'unknown robot';
    var greeting_message = greeting_message || 'hello, human!';
    var goodbye_message = goodbye_message || 'destroy all humans!';
}

It's nice, but what if we wanted to only have a custom goodbye message? We would have fill undefined for the other parameters. If this function changes (say, another optional parameter is inserted between name and greeting_message), we have to change all our calls too. A better way would be to accept an optional object at the end.

function createRobot(unique_id, options){
    var name = options.name; // options.name and options['name'] are identical 
    var greeting_message = options.greeting_message;
    var goodbye_message = options.goodbye_message;
}

The main problem of the implementation above is handling if options parameter didn’t have a specific key we're looking for. So we need to internally hold an object with default values. Then we iterate over the passed options object and replace any default values with the new one like so:

function createRobot(unique_id, options){
    // default values 
    var opt = {
        name: 'unknown robot',
        greeting_message: 'hello, human!',
        goodbye_message: 'destroy all humans!'
    };
    // set options to {} if no value was provided
    options = options || {};
    for (var key in options){
        opt[key] = options[key];
    }
    var name = opt.name;
    var greeting_message = opt.greeting_message;
    var goodbye_message = opt.goodbye_message;
    // do robot stuff. 
}
// examples: 
createRobot('ufo-117');
createRobot('healer-93', {goodbye_message: 'Have a good day!'});
createRobot('sn-131', {name: 'skynet', greeting_message: 'Useless human!'}); 

As you can see, this provides us flexibility for how many and which specific optional parameters we want to fill. Most javascript frameworks extract this functionality out for simplier use, we can do the same:

function defaultOptions(defaulted, provided){
    defaulted = defaulted || {};
    provided = provided || {};
    for (var key in provided){
        defaulted[key] = provided[key];
    }
    return defaulted;
}
// usage 
function createRobot(id, options){
    var opt = {
        name: 'unknown robot',
        greeting_message: 'hello, human!',
        goodbye_message: 'destroy all humans!'
    };
    opt = defaultOptions(opt, options);
    // we can access the options in the opt object. 
} 

Our implementation doesn't handle deep copying to ensure the returned object isn't inadvertently changed, which most JS frameworks handle, but for this use case, it's not a deal-breaker. In JavaScript, this is the preferred way to handle multiple optional parameters.


And that wraps up this week. Next will be Try/Catch, DOM Ready, and Namespaces.

  • Share/Bookmark
blog comments powered by Disqus

Recent Posts

Topics

Archives

Following

Links