6.3 The Function ObjectIn Chapter 3, Variables and Data Types we saw that each of the primitive (i.e., non-object) JavaScript data types has a corresponding "wrapper" object type that is used to provide properties and methods for the data type. Recall that JavaScript automatically converts primitive values to the corresponding object type, when those values are used in an "object context"--i.e., when you try to access their properties or methods. Because the conversion is so transparent to the programmer, it can seem as if primitive types, like strings, have properties and methods. Since, as we've seen, functions are not just a syntactic feature of JavaScript, but also a data type, JavaScript provides the Function object type as a wrapper. The Function object has two properties: arguments, which contains an array of arguments passed to the function, and caller which refers to the function that called the current function. Additionally, in Navigator 3.0, the Function object has a constructor function that can be used (with the new keyword) to define new functions dynamically, at run-time. The subsections below explain exactly how these two properties and the constructor function work. Before we consider the properties of the Function object, there are a couple of important points we must note about their use. The first point is that the arguments and caller properties of the Function object are only defined while the function is being executed. If you try to access these properties from outside the function, their value will be null. The second point to note is that in order to refer to these Function properties from inside a function, the function must refer to itself. It would seem logical that JavaScript would define a special keyword that refers to "the currently running function" to support this self-reference. There are two likely candidates, but unfortunately, neither of them do what we want: the this keyword, when used in a function refers to the object through which the function was invoked (we'll see more about this when we consider methods in Chapter 7, Objects), and the self keyword (really a property name, not a keyword, as we'll see in Chapter 11, Windows and the JavaScript Name Space) refers to the current browser window, not the current function. The current version of JavaScript simply does not have a keyword to refer to the current function, although this may be added in a future version of the language. So, a function can refer to itself simply by using its name. As we saw in the previous section, this name is nothing more that a variable name or an object property, or even a numbered element of an array. Remember that a function is just a data value--if you can refer to this value in order to invoke the function, then you can generally refer to it in the same way from inside the function body. A function f might refer to elements of its arguments[] array like this:
function f() { return f.arguments[0] * f.arguments[1]; } When we introduce the constructor function of the Function object, we'll actually show a way to create unnamed functions, and you may encounter occasional circumstances in which the body of a function does not know how to refer to itself. If you encounter one of these rare cases in Navigator 3.0, you can refer to the current function by passing the string "this" to the eval() method (a method of the Function object, as it is of all objects). For example, you could refer to the caller property of the current function, without explicitly naming it, like this:
eval("this").caller With these notes about the use of the Function object's properties in mind, we can finally go ahead and consider the properties themselves. The arguments[] ArrayThe arguments[] property of a Function object refers to an array that contains the complete set of argument values passed to the function for the current invocation. JavaScript allows any number of argument values to be passed to any function, regardless of the number of argument names that appear in the function definition. If you define a function named f with a single argument named x, then within the function, the value of the argument x is the same as f.arguments[0]. If you invoke this function and pass it two arguments instead of just one, then the second argument won't have a name within the function but will be available as f.arguments[1]. Like most arrays, the arguments[] array has a length property that specifies the number of elements. Thus, for a function f, f.arguments.length specifies the number of argument values that were passed for the current invocation. The arguments[] array is useful in a number of ways. As Example 6.3 shows, you can use it to check that a function is invoked with the correct number of arguments, since JavaScript doesn't do this for you. Example 6.3: Checking for the Correct Number of Arguments
function f(x, y, z) { // first, check that the right # of arguments were passed. if (f.arguments.length != 3) { alert("function f called with " + f.arguments.length + "arguments, but it expects 3 arguments."); return null; } // now do the actual function... } The arguments[] array also opens up an important possibility for JavaScript functions: they can be written so that they work with any number of arguments. Example 6.4 shows how you can write a max() function that accepts any number of arguments and returns the value of the largest argument it is passed. Example 6.4: A Multi-Argument max() Function
function max() { var m = -Number.MAX_VALUE; // Navigator 3.0 only. In 2.0 use -1.79E+308 // loop through all the arguments, looking for, and // remembering, the biggest. for(var i = 0; i < max.arguments.length; i++) if (max.arguments[i] > m) m = max.arguments[i]; // return the biggest. return m; } var largest = max(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6); You can also write functions that have some named arguments, followed by some unnamed arguments. Example 6.5 shows such a function; it is a constructor function that creates an array, initializes a size property as specified by a named argument len, and then initializes an arbitrary number of elements, starting with element 1, of the array to the values of any additional arguments. (JavaScript programs in Navigator 2.0 often use a function like this, as seen in Chapter 8, Arrays.) Example 6.5: Creating and Initializing an Array
function InitializedArray(len) { this.size = len; // In 2.0, this sets array element 0. for (var i = 1; i < InitializedArray.arguments.length; i++) this[i] = InitializedArray.arguments[i]; } A final note about the arguments[] array: the arguments property of a Function object actually holds a copy of the Function object itself. In other words, if f is a function, and F is the corresponding Function object, then each of the following lines of code refers to the same thing:
f.arguments F.arguments F F.arguments.arguments.arguments The caller PropertyThe other property of the Function object is caller. This property is a reference to the function (the function value itself, not the Function object wrapper) that invoked the current one. If the function was invoked from the top level of the script, rather than from a function, then this property will be null. Because caller is a reference to a function value, you can do anything with it that you can do with any other function reference. You can call it, or pass it to other functions, causing a kind of recursion. Unfortunately, since the caller property refers to a function that is not the currently executing function, you cannot inspect the arguments or caller property of the function referred to by the caller property. That is, the following JavaScript expressions evaluate to null:
f.caller.caller // doesn't work f.caller.arguments[1] // doesn't work The Function() ConstructorWe said in Chapter 4, Expressions and Operators that the new operator is used to create new objects; this operator is used with a special "constructor function" that specifies the type of object to create. Many JavaScript object types define constructor functions that can be used to create objects of that type. The Function object type is no exception--it provides the Function() constructor which allows us to create new Function objects. This constructor works in Navigator 3.0, but not in Internet Explorer 3.0. It will be implemented in a future version of IE. The Function() constructor provides a technique for defining functions without using the function keyword. You can create a new Function object with the Function() constructor like this:
var f = new Function("x", "y", "return x*y;");
function f(x, y) { return x*y; } The Function() constructor expects any number of string arguments. The last argument in the list becomes the body of the function--it can contain arbitrary JavaScript statements, separated from each other with semicolons. All other arguments to the Function() constructor are strings that specify the names of the arguments to the function being defined. If you are defining a function that takes no arguments, then you simply pass a single string--the function body--to the constructor. There are a couple of reasons you might want to use the Function() constructor. Recall that the function keyword defines a variable, just like the var does. So the first reason to use the Function() constructor is to avoid having to give your function a temporary variable name when you are just going to immediately assign it to an object property (making a method of that object, as we'll see in Chapter 7, Objects). For example, consider the following two lines of code:
function tmp_area() { return Math.PI * this.radius * this.radius; } Circle.area = tmp_area
Circle.area = new Function("return Math.PI * this.radius * this.radius;"); Another reason you might want to use the Function() constructor is to define temporary or "anonymous" functions that are never given a name. Recall the Array.sort() method mentioned earlier in this chapter: it takes a function as an argument, and that function defines how the elements of the array are sorted. Strings and numbers already have a well-defined sort order, but suppose we were trying to sort an array of objects each of which represented a complex number. To do this, we might use the magnitude of the number, or its overall "distance" from the origin as the value which we would compare to do the sort. It is simple enough to write an appropriate function to perform this comparison, but if we only plan to sort this array of complex number objects once, we might not want to bother defining the function with the function keyword and giving it a permanent name. Instead, we might simply use code like the following to dynamically create a Function object and pass it to the sort() method without ever giving it a name. (Recall that just as JavaScript automatically converts primitive types to their corresponding wrapper objects, so too does it convert in the other direction. So the Function object created in the example will be automatically converted to a function value appropriate for the sort() method.
complex_nums.sort( new Function("a", "b", "Math.sqrt(a.x*a.x+a.y*a.y)-Math.sqrt(b.x*b.x+b.y*b.y);")); The only difference between functions defined with the function keyword and those defined with the Function() constructor has to do with how they are printed. (Try it! Use document.write() or alert().) When a function is printed (or otherwise converted to a string) the function name, arguments, and body are displayed, along with the function keyword. The result of converting a function to a string is a string that contains a legal JavaScript function definition. When a function is defined with function, it is given a name as part of the function definition syntax, and this name appears when the function is printed. Functions defined with Function(), however, do not have a name, and so are printed with the name "anonymous". For this reason, functions defined in this way are sometimes referred to as "anonymous functions". Function PropertiesThere are several interesting facts about functions that you should be aware of. You can combine these facts into a useful programming technique. Functions are objectsOne of the interesting features of JavaScript functions is that you can assign properties to them. For example:
function f() { alert('hello world!'); } f.i = 3;
var i = f.i + 2; What is unusual about this is that we are assigning a property to a primitive function value. JavaScript does actually allow us to assign properties to other primitive types, but those properties don't persist. Consider this code:
n = 1; // A number n.i = 2; // Convert it to a Number object and give that object a property typeof n.i // This tells us n.i is undefined; the property is transient. The reason this doesn't happen with functions is that all JavaScript functions are objects. The Function object is obviously an object type, but even primitive function types are objects that can have properties assigned to them. Because functions are such an important and integral part of the language, however, they are usually treated as a special primitive type. Function arguments and variables are propertiesIn all versions of JavaScript, global variables are actually properties of some top-level object. In client-side JavaScript, as we'll see, this top-level object is the browser window or frame that contains the JavaScript code. This raises the obvious question: if global variables are properties of an object, what are local function variables? It would make sense that they, too, are properties of some object. The only obvious object is the function (or Function) itself. The following code demonstrates:
function f(x) { var y = 3; // a local variable return f.x + f.y; // refer to the argument and variable as properties }
result = f(2); // returns 5
typeof f.x // yields "undefined" typeof f.y // yields "undefined" What this means is that, like the arguments[] array and the caller property, the local variable and argument properties are only accessible while the function is running. When the function returns, JavaScript deletes these properties. Function properties simulate static variablesKnowing that local variables are implemented as transient properties of a function is not particularly useful in itself, but it does lead us to a useful programming technique. In C and C++, a static variable in a function is one that is local to the function, but which has a value that persists across invocations of the function--that is, its value is not reset every time the function is called, and you can use it to save state so that a function could keep track of how many times it had been invoked, for example. A static variable in a function is a global variable, because it retains its value. And it is also like a local variable because it is invisible outside the function, which means that you do not have to give it a unique name or worry about collisions with other global variables or about cluttering up the name space. This is often a very useful combination of features. JavaScript does not support static variables directly, but it turns out that we can simulate them with function properties. We've seen that function properties for local variables and arguments are created when a function is invoked and are deleted when the function returns. You can create other properties of a function, however, that will not be deleted like this. Because local variables are looked up as properties of the function, any properties you add will appear to be local variables. They differ from local variables, however, in that they are not deleted and reset every time the function is called, so they can retain their value. At the same time, though, they are properties of a function instead of global variables, so they do not clutter the name space. These are exactly the features we desire in a static variable. Example 6.6 shows a function that uses a "static variable" to keep track of how many times it has been called. You'll probably find many more realistic uses for static variables in your own programming. As a rule of thumb, never use a global variable where a static variable would work as well. Example 6.6: Using Static Variables
function count() { // counter is a static variable, defined below. // Note that we use it just like a local variable. alert("You've called me " + counter + " time(s)."); // Increment the static variable. This incremented value // will be retained and will be used the next time we are called. counter++; } // To define the static variable, just set it as a property of the function: // Note that the only shortcoming of this technique is that static // variables can only be defined after they are used in the function. count.counter = 1; |
|