Javascript: The Definitive Guide

Previous Chapter 6 Next
 

6.2 Functions as Data Types

The most important features of functions is that they can be defined and invoked, as shown in the previous section. Function definition and invocation are syntactic features of JavaScript, and of most other programming languages. In JavaScript, however, functions are not only syntax, but also data. In some languages, like Java, functions are part of a program, but cannot be manipulated by the program--you cannot, for example, pass one function as an argument to another function in Java. Other languages, like C and C++, are more flexible--while a function defined in C is not actually a data type, "function pointers" can be manipulated by the program, and it is possible to pass these function pointers to other functions and to assign them to variables.

JavaScript goes even further than C. Functions in JavaScript are data, and thus can be treated like any other data value--assigned to variables, stored in the properties of objects or the elements of arrays, passed to functions, and so on. Because JavaScript is an interpreted language, and because it treats functions as a distinct data type, the language (in Navigator 3.0) even allows functions to be defined dynamically, at run-time! We'll see how this is done when we consider the Function object later in this chapter.

We've seen that the function keyword is the syntax used to define a function in a JavaScript program. To understand how functions are JavaScript data as well as JavaScript syntax, we've got to understand what the function keyword really does. function creates a function, as we've seen, but it also defines a variable. In this way, the function keyword is like the var keyword. Consider the following function definition:

function square(x) { return x*x; }
This code does the following:

When we consider function definition in this light, it becomes clear that the name of a function is really immaterial--it is simply the name of a variable that holds the function. The function can be assigned to another variable, and will still work the same:

function square(x) { return x*x; }
a = square(4);   // a contains the number 16
b = square;      // now b refers to the same function as square does.
c = b(5);        // c contains the number 25
Functions can also be assigned to object properties:

o = new Object;
o.sq = square;
y = o.sq(16);   // y equals 256
Functions don't even require names, as when we assign them to array elements:

a = new Array(10);
a[0] = square;
a[1] = 20;
a[2] = a[0](a[1]);  // a[2] contains 400
Note that the function invocation syntax in this last example looks strange, but is still a legal use of the JavaScript () operator!

Example 6-2 is a detailed example of the things that can be done when functions are used as data. It demonstrates how functions can be passed as arguments to other functions, and also how they can be stored in associative arrays (which were introduced in Chapter 3, Variables and Data Types, and are explained in detail in Chapter 7, Objects.) This example may be a little tricky, but the comments explain what is going on; it is worth studying carefully.

Example 6-2: Using Functions as Data

// We define some simple functions here
function add(x,y) { return x + y; }
function subtract(x,y) { return x - y; }
function multiply(x,y) { return x * y; }
function divide(x,y) { return x / y; }
// Here's a function that takes one of the above functions
// as an argument and invokes it on two operands
function operate(operator, operand1, operand2) 
{
    return operator(operand1, operand2);
}
// We could invoke this function like this to compute
// the value (2+3) + (4*5):
var i = operate(add, operate(add, 2, 3), operate(multiply, 4, 5));
// Now we store the functions defined above in an associative array
var operators = new Object();
operators["add"] = add;
operators["subtract"] = subtract;
operators["multiply"] = multiply;
operators["divide"] = divide;
operators["pow"] = Math.pow;  // works for predefined functions too.
// This function takes the name of an operator, looks up
// that operator in the array, and then invokes it on the
// supplied operands. Note the syntax used to invoke the
// operator function.
function operate2(op_name, operand1, operand2)
{
    if (operators[op_name] == null) return "unknown operator";
    else return operators[op_name](operand1, operand2);
}
// We could invoke this function as follows to compute
// the value ("hello" + " " + "world"):
var j = operate2("add", "hello", operate2("add", " ", "world"))
// Using the predefined Math.pow() function
var k = operate2("pow", 10, 2)

If the preceding example does not convince you of the utility of being able to pass functions as arguments to other functions, and otherwise treat functions as data values, consider the Array.sort() method. This function sorts the elements of an array, but because there are many possible orders to sort things into (numerical order, alphabetical order, date order, ascending, descending, and so on) it takes a function as an argument to tell it how to perform the sort. This function has a very simple job--it is passed two elements of the array, which it compares, and then returns a value specifying which element is larger and which is smaller. This function argument makes the Array.sort() method perfectly general and infinitely flexible--it can sort any type of data into any conceivable order!


Previous Home Next
Defining and Invoking Functions Book Index The Function Object