Functions
From Secrets of the JavaScript Ninja
In this chapter:
- Overview of the importance of functions
- Using functions as objects
- Context within a function
- Handling a variable number of arguments
- Determining the type of a function
The quality of all code that you'll ever write, in JavaScript, relies upon the realization that JavaScript is a functional language. All functions, in JavaScript, are first-class: They can coexist with, and can be treated like, any other JavaScript object.
One of the most important features of the JavaScript language is that you can create anonymous functions at any time. These functions can be passed as values to other functions and be used as the fundamental building blocks for reusable code libraries. Understanding how functions, and by extension anonymous functions, work at their most fundamental level will drastically affect your ability to write clear, reusable, code.
Contents |
[edit] Function Definition
While it's most common to define a function as a standalone object, to be accessed elsewhere within the same scope, the definition of functions as variables, or object properties, is the most powerful means of constructing reusable JavaScript code.
Let's take a look at some different ways of defining functions (one, the traditional way, and two using anonymous functions):
Listing 2-1: Three different ways to define a function.
function isNimble(){ return true; }
var canFly = function(){ return true; };
window.isDeadly = function(){ return true; };
assert( isNimble() && canFly() && isDeadly(), "All are functions, all return true" );
All those different means of definition are, at a quick glance, entirely equivalent (when evaluated within the global scope - but we'll cover that in more detail, later). All of the functions are able to be called and all behave as you would expect them to. However, things begin to change when you shift the order of the definitions.
Listing 2-2: A look at how the location of function definition doesn't matter.
var canFly = function(){ return true; };
window.isDeadly = function(){ return true; };
assert( isNimble() && canFly() && isDeadly(), "Still works, even though isNimble is moved." );
function isNimble(){ return true; }
We're able to move the isNimble function because of a simple property of function definitions: No matter where, within a scope, you define a function it will be accessible throughout. This is made incredibly apparent in the following:
Listing 2-3: Defining a function below a return statement.
function stealthCheck(){
var ret = stealth() == stealth();
return assert( ret, "We'll never get below this line, but that's OK!" );
function stealth(){ return true; }
}
stealthCheck();
In the above, stealth() will never be reached in the normal flow of code execution (since it's behind a return statement). However, because it's specially privileged as a function it'll still be defined as we would expect it to. Note that the other ways of defining the function (using anonymous functions) do not get this benefit - they only exist after the point in the code at which they've been defined.
Listing 2-4: Types of function definition that can't be placed anywhere.
assert( typeof canFly == "undefined", "canFly doesn't get that benefit." );
assert( typeof isDeadly == "undefined", "Nor does isDeadly." );
var canFly = function(){ return true; };
window.isDeadly = function(){ return true; };
This is all very important as it begins to lay down the fundamentals for the naming, flow, and structure that functional code can exist in.
[edit] Anonymous Functions and Recursion
Recursion is, generally speaking, a solved problem. When a function calls itself (from inside itself, naturally) there is some level of recursion occurring. However, the solution becomes much less clear when you begin dealing with anonymous functions.
Listing 2-5: Simple function recursion.
function yell(n){
return n > 0 ? yell(n-1) + "a" : "hiy";
}
assert( yell(4) == "hiyaaaa", "Calling the function by itself comes naturally." );
A single named function works just fine, but what if we were to use anonymous functions, placed within an object structure?
Listing 2-6: Function recursion within an object.
var ninja = {
yell: function(n){
return n > 0 ? ninja.yell(n-1) + "a" : "hiy";
}
};
assert( ninja.yell(4) == "hiyaaaa", "A single object isn't too bad, either." );
This is all fine until we decide to create a new samurai object; duplicating the yell method from the ninja. We can see how things start to break down, since the anonymous yell function, within the ninja object, is still referencing the yell function from the ninja object. Thus, if the ninja variable is redefined we find ourselves in a hard place.
Listing 2-7: Recursion using a function reference that no longer exists (using the code from Listing 2-6).
var samurai = { yell: ninja.yell };
var ninja = {};
try {
samurai.yell(4);
} catch(e){
assert( true, "Uh, this isn't good! Where'd ninja.yell go?" );
}
How can we work around this, making the yell method more robust? The most obvious solution is to change all the instances of ninja, inside of the ninja.yell function, to 'this' (the generalized form of the object itself). That would be one way, another way could be to give the anonymous function a name. This may seem completely contradictory, but it works quite nicely, observe:
Listing 2-8: A named, anonymous, function.
var ninja = {
yell: function yell(n){
return n > 0 ? yell(n-1) + "a" : "hiy";
}
};
assert( ninja.yell(4) == "hiyaaaa", "Works as we would expect it to!" );
var samurai = { yell: ninja.yell };
var ninja = {};
assert( samurai.yell(4) == "hiyaaaa", "The method correctly calls itself." );
This ability to name an anonymous function extends even further. It can even be done in normal variable assignments with, seemingly bizarre, results:
Listing 2-9: Verifying the identity of a named, anonymous, function.
var ninja = function myNinja(){
assert( ninja == myNinja, "This function is named two things - at once!" );
};
ninja();
assert( typeof myNinja == "undefined", "But myNinja isn't defined outside of the function." );
This brings up the most important point: Anonymous functions can be named but those names are only visible within the functions themselves.
So while giving named anonymous functions may provide an extra level of clarity and simplicity, the naming process is an extra step that we need to take in order to achieve that. Thankfully, we can circumvent that entirely by using the callee property of the arguments object.
Listing 2-10: Using arguments.callee to reference a function itself.
var ninja = {
yell: function(n){
return n > 0 ? arguments.callee(n-1) + "a" : "hiy";
}
};
assert( ninja.yell(4) == "hiyaaaa", "arguments.callee is the function itself." );
arguments.callee is available within every function (named or not) and can serve as a reliable way to always access the function itself. Later on in this chapter, and in the chapter on closures, we'll take some additional looks at what can be done with this particular property.
All together, these different techniques for handling anonymous functions will greatly benefit us as we start to scale in complexity, providing us with an extra level of clarity and conciseness.
[edit] Functions as Objects
In JavaScript functions behave just like objects; they can have properties, they have an object prototype, and generally have all the abilities of plain vanilla objects - the exception being that they are callable.
Let's look at some of the similarities and how they can be made especially useful. To start with, let's look at the assignment of functions to a variable.
Listing 2-11: Assigning a function to a variable.
var obj = {};
var fn = function(){};
assert( obj && fn, "Both the object and function exist." );
- Note One thing that's important to remember is the semicolon after
function(){}definition. It's a good practice to have semicolons at the end of lines of code - especially so after variable assignments. Doing so with anonymous functions is no exception. When compressing your code (which will be discussed in the distribution chapter) having thorough use of your semicolons will allow for greater flexibility in compression techniques.
Now, just like with an object, we can attach properties to a function.
Listing 2-12: Attaching properties to a function.
var obj = {};
var fn = function(){};
obj.prop = "some value";
fn.prop = "some value";
assert( obj.prop == fn.prop, "Both are objects, both have the property." );
This aspect of functions can be used in a number of different ways throughout a library (especially so when it comes to topics like event callback management). For now, let's look at some of more interesting things that can be done.
[edit] Storing Functions
A challenge when storing a collection of unique functions is determining which functions are actually new and need to be added (A solution to this is very useful for event callback management). An obvious technique would be to store all the functions in an array and then loop through the array looking for duplicate functions. However, this performs poorly and is impractical for most applications. Instead, we can make good use of function properties to achieve an acceptable result.
Listing 2-13: Storing unique functions within a structure.
var store = {
id: 1,
cache: {},
add: function( fn ) {
if ( !fn.id ) {
fn.id = store.id++;
return !!(store.cache[fn.uuid] = fn);
}
};
};
function ninja(){}
assert( store.add( ninja ), "Function was safely added." );
assert( !store.add( ninja ), "But it was only added once." );
In the above example, especially within the add function itself, we check to see if the incoming function already has a unique id assigned to it (which is just an extra property that we could have added to the function, previously). If no such property exists we then generate a unique id and attach it. Finally, we store the function in the cache (we also return a boolean version of the function itself, if the addition was successful, so that we have some way of externally determining its success).
- Tip The
!!construct is a simple way of turning any JavaScript expression into its boolean equivalent. For example:!!"hello" === trueand!!0 === false. In the above example we end up converting a function (which will always be true) into its boolean equivalent:true.
Another useful trick, that can be provided by function properties, is the ability of a function to modify itself. This technique could be used to memorize previously-computed values; saving time with future computations.
[edit] Self-Memoizing Functions
As a basic example, let's look at a poorly-written algorithm for computing prime numbers. Note how the function appears just like a normal function but has the addition of an answer cache to which it saves, and retrieves, solved numbers.
Listing 2-14: A prime computation function which memorizes its previously-computed values.
function isPrime( num ) {
if ( isPrime.answers[ num ] != null )
return isPrime.answers[ num ];
var prime = num != 1; // Everything but 1 can be prime
for ( var i = 2; i < num; i++ ) {
if ( num % i == 0 ) {
prime = false;
break;
}
}
return isPrime.answers[ num ] = prime;
}
isPrime.answers = {};
assert( isPrime(5), "Make sure the function works, 5 is prime." );
assert( isPrime.answers[5], "Make sure the answer is cached." );
There are two advantages to writing a function in this manner: First, that the user gets the improved speed benefits from subsequent function calls and secondly, that it happens completely seamlessly (the user doesn't have to perform a special request or do any extra initialization in order to make it work).
Let's take a look at a modern example. Querying for a set of DOM elements, by name, is an incredibly common operation. We can take advantage of our new-found function property power by building a cache that we can store the matched element sets in.
Listing 2-15: A simple example of using a self-caching function.
function getElements( name ) {
return getElements.cache[ name ] = getElements.cache[ name ] ||
document.getElementsByTagName( name );
}
getElements.cache = {};
The code ends up being quite simple and doesn't add that much extra complexity to the overall querying process. However, if were to do some performance analysis upon it we would find that this simple layer of caching yields us a 7x performance increase.
| Average | Min | Max | Deviation | |
| Original getElements | 12.58 | 12 | 13 | 0.50 |
| Cached getElements | 1.73 | 1 | 2 | 0.45 |
Even without digging too deep the usefulness of function properties should be quite evident. We'll be revisiting this concept throughout the upcoming chapters, as its applicability extends throughout the JavaScript language.
[edit] Context
A function's context is, simultaneously, one of its most powerful and confusing features. By having an implicit 'this' variable be included within every function it gives you great flexibility for how the function can be called and executed. There's a lot to understand about context - but understanding it well can make a drastic improvement in the quality of your functional code.
To start, it's important to realize what that the function context represents: The object within which the function is being executed. For example, defining a function within an object structure will ensure that its context always refers to that object - unless otherwise overwritten:
Listing 2-16: Examining context within a function.
var katana = {
isSharp: true,
use: function(){
this.isSharp = !!this.isSharp;
}
};
katana.use()
assert( !katana.isSharp, "Verify the value of isSharp has been changed." );
However, what about a function that isn't explicitly declared as a property of an object? Then the function's context refers to the global object.
Listing 2-17: What context refers to within a function.
function katana(){
this.isSharp = true;
}
katana();
assert( isSharp === true, "A global object now exists with that name and value." );
var shuriken = {
toss: function(){
this.isSharp = true;
}
};
shuriken.toss();
assert( shuriken.isSharp === true, "When it's an object property, the value is set within the object." );
All of this becomes quite important when we begin to deal with functions in a variety of contexts - knowing what the context will represent affects the end result of your functional code.
- Note In ECMAScript 4 (JavaScript 2) a slight change has taken place: Just because a function isn't defined as a property of an object doesn't meant that its context will be the global object. The change is that a function defined within an other function will inherit the context of the outer function. What's interesting about this is that it ends up affecting very little code - having the context of inner functions default to the global object turned out to be quite useless in practice (hence the change).
Now that we understand the basics of function context, it's time to dig deep and explore its most complex usage: The fact that a function context can be redefined anytime that it's called.
JavaScript provides two methods (call and apply), on every function, that can be used to call the function, define its context, and specify its incoming arguments.
Listing 2-18: Modifying the context of a function, when call.
var object = {};
function fn(){
return this;
}
assert( fn() == this, "The context is the global object." );
assert( fn.call(object) == object, "The context is changed to a specific object." );
The two methods are quite similar to each other, as to how they're used, but with a distinction: How they set incoming argument values. Simply, .call() passes in arguments individually whereas .apply() passes in arguments as an array.
Listing 2-19: Two methods of modifying a function's context.
function add(a, b){
return a + b;
}
assert( add.call(this, 1, 2) == 3, ".call() takes individual arguments" );
assert( add.apply(this, [1, 2]) == 3, ".apply() takes an array of arguments" );
Let's look at some simple examples of this is used, practically, in JavaScript code.
[edit] Looping
The first example is that of using call to make an array looping function with a function callback. This is a frequent addition to most JavaScript libraries. The result is a function that can help to simplify the process of looping through an array.
Listing 2-20: Looping with a function callback.
function loop(array, fn){
for ( var i = 0; i < array.length; i++ )
if ( fn.call( array, array[i], i ) === false )
break;
}
var num = 0;
loop([0, 1, 2], function(value, i){
assert(value == num++, "Make sure the contents are as we expect it.");
});
The end result to this looping is, arguably, syntactically cleaner than a traditional for loop and can be of great service for when you want to simplify the rote looping process. Additionally, the callback is always able to access the original array via the 'this' context, regardless of its actual instantiation (so it even works on anonymous arrays, like in the example above).
One thing that's especially important about the previous example is the use of a (typically anonymous) function argument being called in response to some action - generally referred to as a 'callback.' Callbacks are a staple of JavaScript libraries. Overwhelmingly, callbacks are related to asynchronous, or indeterministic, behavior (such as a user clicking a button, an Ajax request completing, or some number of values being found in an array). We'll see this pattern repeat again-and-again in user code.
[edit] Fake Array Methods
Let's pretend that we wanted to create an object that behaved similarly to an array (say, a collection of aggregated DOM elements) but take on additional functionality not related to most arrays. One option might be to create a new array every time you wish to create a new version of your objects and imbue it with your desired properties and methods. Generally, however, this can be quite slow. Instead, let's examine the possibility of using a normal object and just giving it the functionality that we desire. For example:
Listing 2-21: Simulating array-like methods.
<input id="first"/><input id="second"/>
<script>
var elems = {
find: function(id){
this.add( document.getElementById(id) );
},
length: 0,
add: function(elem){
Array.prototype.push.call( this, elem );
}
};
elems.add("first");
assert( elems.length == 1 && elems[0].nodeType, "Verify that we have an element in our stash" );
elems.add("second");
assert( elems.length == 2 && elems[1].nodeType, "Verify the other insertion" );
</script>
A couple important aspect, to take note of, in this example. We're accessing a native object method (Array.prototype.push) and are treating it just like any other function or method - by using .call(). The most interesting part is the use of 'this' as the context. Normally, since a push method is part of an array object, setting the context to any other object is treated as if it was an array. This means that when we push this new element on to the current object, its length will be modified and a new numbered property will exist containing the added item.
This behavior is almost subversive, in a way, but it's one that best exemplifies what we're capable of doing with mutable object contexts and offers an excellent segue into discussing the complexities of dealing with function arguments.
[edit] Variable Arguments
JavaScript, as a whole, is very flexible in what it can do and much of that flexibility defines the language, as we know it, today. Incidentally, the lifeblood of a JavaScript function is its ability to accept any number of arguments, this flexibility offers the developer great control over how their applications can be written.
Let's take a look at a prime example of how we can use flexible arguments to our advantage.
[edit] Min/Max Number in an Array
Finding the smallest, or the largest, the values contained within an array can be a tricky problem - there is no, included, method for performing this action in the JavaScript language. This means that we'll have to write our own, from scratch. At first glance it seems as if it might be necessary to loop through the contents of the array in order to determine the correct numbers, however we have a trick up our sleeve. See, the call and apply methods also exist as methods of built-in JavaScript functions - and some built-in methods are able to take any number of arguments. In our case, the methods Math.min() and Math.max() are able to take any number of arguments and find their appropriate values. Thus, if we were to use .apply() on these methods then we'd be able to find the smallest, or largest, value within an array, in one fell swoop, like so:
Listing 2-22: A generic min and max function for arrays.
function smallest(array){
return Math.min.apply( Math, array );
}
function largest(array){
return Math.max.apply( Math, array );
}
assert(smallest([0, 1, 2, 3]) == 0, "Locate the smallest value.");
assert(largest([0, 1, 2, 3]) == 3, "Locate the largest value.");
Also note that we specify the context as being the Math object. This isn't necessary (the min and max methods will continue to work regardless of what's passed in as the context) but there's no reason not to be tidy in this situation.
[edit] Function Overloading
All functions are, also, provided access to an important local variable, arguments, which gives them the power necessary to handle any number of provided arguments. Even if you only ask for - or expect - a certain number of arguments you'll always be able to access all specified arguments with the arguments variable.
Let's take a quick look at an example of effective overloading. In the following we're going to merge the contents of multiple objects into a single, root, object. This can be an effective utility for performing multiple inheritance (which we'll discuss more when we talk about [Object Prototypes]).
Listing 2-23: Changing function actions based upon the arguments.
function merge(root){
for ( var i = 0; i < arguments.length; i++ )
for ( var key in arguments[i] )
root[key] = arguments[i][key];
return root;
}
var merged = merge({name: "John"}, {city: "Boston"});
assert( merged.name == "John", "The original name is intact." );
assert( merged.city == "Boston", "And the city has been copied over." );
Obviously this can be an effective mechanism for, potentially, complex methods. Let's take a look at another example where the use of the arguments variable isn't so clean-cut. In the following example we're building a function which multiplies the largest remaining argument by the first argument - a simple math operation. We can gain an advantage by using the Math.max() technique that we used earlier, but there's one small hitch: The arguments variable isn't a true array. Even thought it looks and feels like one it lacks basic methods necessary to make this operation possible (like .slice()). Let's examine this example and see what we can do to make our desired result.
Listing 2-24: Handling a variable number of function arguments.
function multiMax(multi){
return multi * Math.max.apply( Math,
Array.prototype.slice.call( arguments, 1 ));
}
assert( multiMax(3, 1, 2, 3) == 9, "3*3=9 (First arg, by largest.)" );
Here we use a technique, similar to the one we used with the Math.max() method, but allows us to use native Array methods on the arguments variable - even though it isn't a true array. The important aspect is that we want to find the largest value in everything but the first argument. The slice method allows us to chop off that first argument, so by applying it via .call() to the arguments variable we can get our desired result.
[edit] Function Length
There's an interesting property on all functions that can be quite powerful, when working with function arguments. This isn't very well known, but all functions have a length property on them. This property equates to the number of arguments that the function is expecting. Thus, if you define a function that accepts a single argument, it'll have a length of 1, like so:
Listing 2-25: Inspecting the number of arguments that a function is expecting.
function makeNinja(name){}
function makeSamurai(name, rank){}
assert( makeNinja.length == 1, "Only expecting a single argument" );
assert( makeSamurai.length == 2, "Multiple arguments expected" );
What's important about this technique, however, is that the length property will only represent the arguments that are actually specified. Therefore if you specify the first argument and accept and additional, variable, number of arguments then your length will still only be one (like in the multiMax method shown above).
Incidentally, the function length property can be used to great effect in building a quick-and-dirty function for doing simple method overloading. For those of you who aren't familiar with overloading, it's just a way of mapping a single function call to multiple functions based upon the arguments they accept.
Let's look at an example of the function that we would like to construct and how we might use it:
Listing 2-26: An example of method overloading using the addMethod function from Listing 2-27.
function Ninjas(){
var ninjas = [ "Dean Edwards", "Sam Stephenson", "Alex Russell" ];
addMethod(this, "find", function(){
return ninjas;
});
addMethod(this, "find", function(name){
var ret = [];
for ( var i = 0; i < ninjas; i++ )
if ( ninjas[i].indexOf(name) == 0 )
ret.push( ninjas[i] );
return ret;
});
addMethod(this, "find", function(first, last){
var ret = [];
for ( var i = 0; i < ninjas; i++ )
if ( ninjas[i] == (first + " " + last) )
ret.push( ninjas[i] );
return ret;
});
}
var ninjas = new Ninjas();
assert( ninjas.find().length == 3, "Finds all ninjas" );
assert( ninjas.find("Sam").length == 1, "Finds ninjas by first name" );
assert( ninjas.find("Dean", "Edwards") == 1, "Finds ninjas by first and last name" );
assert( ninjas.find("Alex", "X", "Russell") == null, "Does nothing" );
There's a couple things that we can determine about the functionality of addMethod. First, we can use it to attach multiple functions to a single property - having it look, and behave, just like a normal method. Second, depending on the number of arguments that are passed in to the method a different bound function will be executed. Let's take a look at a possible solution:
Listing 2-27: A simple means of overloading methods on an object, based upon the specified arguments.
function addMethod(object, name, fn){
var old = object[ name ];
object[ name ] = function(){
if ( fn.length == arguments.length )
return fn.apply( this, arguments )
else if ( typeof old == 'function' )
return old.apply( this, arguments );
};
}
Let's dig in and examine how this function works. To start with, a reference to the old method (if there is one) is saved and a new method is put in its place. We'll get into the particulars of how closures work, in the next chapter, but suffice it to say that we can always access the old function within this newly bound one.
Next, the method does a quick check to see if the number of arguments being passed in matches the number of arguments that were specified by the passed-in function. This is the magic. In our above example, when we bound function(name){...} it was only expecting a single argument - thus we only execute it when the number of incoming arguments matches what we expect. If the case arises that number of arguments doesn't match, then we attempt to execute the old function. This process will continue until a signature-matching function is found and executed.
This technique is especially nifty because all of these bound functions aren't actually stored in any typical data structure - instead they're all saved as references within closures. Again, we'll talk more about this in the next chapter.
It should be noted that there are some pretty caveats when using this particular technique:
- The overloading only works for different numbers of arguments - it doesn't differentiate based on type, argument names, or anything else. (ECMAScript 4, JavaScript 2, however, will have this ability: called multi-methods.)
- All methods will have some function call overhead. Thus, you'll want to take that into consideration in high performance situations.
Nonetheless, this function provides a good example of the potential usefulness of the function length property, to great effect as well.
[edit] Function Type
To close out this look at functions I wanted to take a quick peek at a common cross-browser issue relating to them. Specifically relating to how you determine if an object, or property, is actually a function that you can call.
Typically, and overwhelmingly for most cases, the typeof statement is more than sufficient to get the job done, for example:
Listing 2-28: A simple way of determining the type of a function.
function ninja(){}
assert( typeof ninja == "function", "Functions have a type of function" );
This should be the de-facto way of checking if a value is a function, however there exist a few cases where this is not so.
- Firefox 2 and 3
- Doing a typeof on the HTML <object/> element yields an inaccurate "function" result (instead of "object"), like so:
typeof objectElem == "function".
- Doing a typeof on the HTML <object/> element yields an inaccurate "function" result (instead of "object"), like so:
- Firefox 2
- A little known feature: You can call regular expressions as if they were functions, like so:
/test/("a test"). This can be useful, however it also means thattypeof /test/ == "function"in Firefox 2 (was changed to "object" in 3).
- A little known feature: You can call regular expressions as if they were functions, like so:
- Internet Explorer 6 and 7
- Internet Explorer reports methods of DOM elements with a type of "object", like so:
typeof domNode.getAttribute == "object"andtypeof inputElem.focus == "object".
- Internet Explorer reports methods of DOM elements with a type of "object", like so:
- Safari 3
- Safari considers a DOM NodeList to be a function, like so:
typeof document.body.childNodes == "function"
- Safari considers a DOM NodeList to be a function, like so:
Now, for these specific cases, we need a solution which will work in all of our target browsers, allowing us to detect if those particular functions (and non-functions) report themselves correctly.
There's a lot of possible avenues for exploration here, unfortunately almost all of the techniques end up in a dead-end. For example, we know that functions have an apply() and call() method - however those methods don't exist on Internet Explorers problematic functions. One technique, however, that does work fairly well is to convert the function to a string and determine its type based upon its serialized value, for example:
Listing 2-29: A more complex, but more resistant, way of determining if a value is a function.
function isFunction( fn ) {
return !!fn && !fn.nodeName && fn.constructor != String &&
fn.constructor != RegExp && fn.constructor != Array &&
/function/i.test( fn + "" );
}
Now this function isn't perfect, however in situations (like the above), it'll pass all the cases that we need, giving us a correct value to work with. Let's look at how this function works.
To start, we need to make sure that we're dealing with something that has a value (!!fn) and doesn't serialize to a string that could contain the word 'function' - which is what our check is based upon (fn.constructor != String, fn.constructor != Array, and fn.constructor != RegExp). Additionally, we make sure that the alleged function isn't actually a DOM node (fn.nodeName). With all of that out of the way, we can do our function toString test. If we convert a function to a string, in a browser, (fn + "") it'll give us a result that typically looks something like this: "function name(){ ... }". With that in mind, the check for compliance is simple: Just see if the serialized function contains the word 'function' in it. Surprisingly, this works quite well for all of our problematic code. Obviously using this function is much slower that doing a traditional typeof, so please be sure to use it sparingly.
This is just a quick taste of the strange world of cross-browser scripting. While it can be quite challenging the result is always rewarding: Allowing you to painlessly write cross browser applications with little concern for the painful minutia.
[edit] Summary
In this chapter we took a look at various, fascinating, aspects of how functions work in JavaScript. While their use is completely ubiquitous, understanding of their inner-workings is essential to the writing of high-quality JavaScript code.
Specifically, within this chapter, we took a look at different techniques for defining functions (and how different techniques can benefit clarity and organization). We also examined how recursion can be a tricky problem, within anonymous functions, and looked at how to solve it with advanced properties like arguments.callee. Then we explored the importance of treating functions like objects; attaching properties to them to store additional data and the benefits of doing such. We also worked up an understanding of how function context works, and can be manipulated using apply and call. We then looked at a number of examples of using a variable number of arguments, within functions, in order to make them more powerful and useful. Finally, we closed with an examination of cross-browser issues that relate to functions.
In all, it was a thorough examination of the fundamentals of advanced function usage that gives us a great lead-up into understanding closures, which we'll do in the next chapter.
