Now that we're comfortable with how functions work, let's return to the topic of function parameters. Our current discussion requires a little knowledge of objects, so new programmers may want to read Chapter 12 before reading this section.
Earlier we saw that the parameters of a function are declared when the function is created. Recall the syntax:
function funcName (param1, param2, ...paramn) { statements }
Perhaps surprisingly, the number of parameters passed to a function can differ from the number specified in the formal function declaration. Functions can accept any number of parameters, whether more than or fewer than the "expected" number. When a function is called with fewer than the declared number of parameters, the value of each missing parameter is set to undefined. For example:
function viewVars (x, y, z) { trace ("x is " + x); trace ("y is " + y); trace ("z is " + z); } viewVars(10); // In Flash Player 6, displays: // x is 10 // y is undefined // z is undefined // (In Flash Player 5, undefined variables are displayed as a // blank string in the Output window.)
When a function is called with more parameters than the declared number, excess parameter values can be accessed using the arguments object. (Obviously, the excess parameters can't be accessed by name, as explicitly declared parameters can, because their names were not declared.)
During the execution of any function, the built-in arguments object gives us access to the following:
The number of parameters that were passed to the function
An array containing each of those parameter values
A reference to the function being executed
A reference to the calling function, if any
The arguments object is really a special hybrid between an array and an object with some other properties. In Flash 5, arguments was not an array instance. In Flash MX, however, arguments is an instance of the Array class and therefore supports Array's methods and properties.
The arguments array lets us check the value of any function invocation argument, whether or not that parameter is defined formally in the function's declaration statement. To access a parameter, we examine the indexes of the arguments array:
arguments[n]
where n is the index of the argument we're accessing. The first argument (the leftmost argument in the function-call invocation) is stored at index 0 and is referred to as arguments[0]. Subsequent arguments are stored in order, proceeding to the right—so, the second argument is arguments[1], the third is arguments[2], and so on.
From within a function, we can tell how many arguments were passed to the currently executing function by checking the number of elements in arguments, as follows:
numArgs = arguments.length;
We can easily cycle through all the arguments passed to a function and display the results in the Output window, as shown in Example 9-3.
function showArgs () { for (var i = 0; i < arguments.length; i++) { trace("Argument " + (i + 1) + " is " + arguments[i]); } } showArgs(123, 23, "skip intro"); // Displays... Argument 1 is 123 Argument 2 is 23 Argument 3 is skip intro
The arguments array allows us to create very flexible functions that accept an arbitrary number of arguments.
Here's a generic function that removes any number of duplicated movie clip instances from the Stage:
function killClip () { for (var i = 0; i < arguments.length; i++) { arguments[i].removeMovieClip(); } } killClip(clip10, clip5, clip13);
Reader Exercise: Modify our earlier combine( ) function to accept an arbitrary number of inputs. What other functions might benefit from accepting an arbitrary number of parameters? How about a function that averages a list of numbers? (Hint: sum all the arguments and then divide by the number of arguments.)
As we've seen, the arguments array lets us retrieve a function's arguments. The arguments object has two properties: callee, which stores a reference to the executing function, and caller, which stores a reference to the function that called the current function (or null if no function called the current function).
Normally, we know the name of the function we're calling, but if we are executing an anonymous function that was originally created with a function literal, the callee property can prove useful. Example 9-4 shows a function, created with a function literal, that performs recursive executions without knowing its own name.
count = function (x) { trace(x); if (x > 1) { arguments.callee(x - 1); } } count(25);
Obviously we can count down without using recursive anonymous functions. We'll see a more realistic example of function recursion in Section 9.9 later in this chapter. Using arguments.callee to refer to the current function (rather than the function's actual name) alleviates the need to update our code if the function's name changes.
We can use arguments.caller to execute or identify a calling function, as described in the Language Reference.
There's one more parameter subtlety we should consider—the difference between passing data to a function by value and by reference.
When we pass a primitive data value as an argument to a function, the function receives a copy of the data, not the original. Changes made to a parameter in the function have no effect on the original argument outside that function. In Example 9-5, variableName's value is initially set to 25. Changing its value to 10 within the setValue( ) function has no effect on y's value.
var y = 25; function setValue (variableName) { variableName = 10; } setValue(y); trace("y is " + y); // Displays: "y is 25"
Primitive data is therefore said to be passed by value. When we pass composite data as an argument to a function, however, the function receives a reference that points to the same data as the original argument, not just a duplicate of the data. Altering the data via the parameter variable affects the original data and therefore affects other variables that point to the same data, even outside of the function. Composite data is therefore said to be passed by reference.
In Example 9-6, changes to the myArray parameter variable affect the external boys array because they both point to the same data in memory.
// Create an array var boys = ["Andrew", "Graham", "Derek"]; // setValue( ) sets the value of the first element of an array function setValue (myArray) { myArray[0] = "Sid"; // Set the first element of the array } // Pass our array to the function setValue(boys); // Check the value of our array elements trace("Boys: " + boys); // Displays: "Boys: Sid,Graham,Derek"
Note that new values assigned to individual elements of the array from inside a function are reflected outside the function as well. However, suppose that instead of assigning new values to one or more elements, we assign a new value to the parameter itself. Doing so will break its association with the original argument. Subsequent changes to the parameter variable will have no effect on the original argument, as shown in Example 9-7. In this example, although the boys array is passed as an argument, the myArray parameter variable is immediately set to girls. Subsequent changes to myArray affect the girls array, not the boys array.
// Create two arrays var boys = ["Andrew", "Graham", "Derek"]; var girls = ["Alisa", "Gillian", "Daniella"]; // setValue( ) ignores the passed array and modifies the girls array function setValue (myArray) { myArray = girls; // Make myArray point to girls, not boys myArray[0] = "Mary"; // Changes the first element of girls } // Pass the boys array to the setValue( ) function setValue(boys); trace("Boys: " + boys); // Displays: "Boys: Andrew,Graham,Derek" trace("Girls: " + girls); // Displays: "Girls: Mary,Gillian,Daniella"
More information on primitive and composite data can be found in Chapter 3.