Table of Contents

9.7 Function Scope

As shown under Section 2.5 in Chapter 2, all ActionScript variables have a certain scope, or region of a program within which they are valid. A statement's location in a movie determines:

For example, here we issue a statement that creates a variable, score:

var score = 10;

If this statement were attached to clipA, the interpreter would set the value of score in clipA. If the statement were attached to clipB, the interpreter would set the value of score in clipB.

Statements in the body of a function operate in their own, separate scope, called a local scope. A function's local scope is private to the function, distinct from the scope of the clip or object to which the function is attached. A new local scope for the function is created each time the function is invoked and destroyed when the function finishes executing. When resolving variables referenced in the statements of the function body, the interpreter looks first in the function's local scope.

Function parameters, for example, are defined in the local scope of a function—not on the timeline that bears the function. Hence, parameters are accessible only to the statements of a function's body and only while the function is running. Statements outside the function have no access to the function's parameters.

A function's local scope provides a place for temporary "local" variables used solely within a function. This eliminates potential name conflicts between local function variables and timeline variables, and it reduces overall memory usage.

9.7.1 Functions and the Scope Chain

Even though functions operate in their own local scopes, normal timeline variables are still accessible to the statements of a function body. The local scope of a function is the first place the interpreter looks to resolve variable references, but if the variable reference can't be found in the local scope, the search extends to the movie clip in which the function was declared, and then (as of Flash Player 6) to the global object.

For example, suppose we define a variable, firstName, on the timeline of a movie clip, clipA. We also declare a function, getName( ), on clipA. Inside getName( ), we refer to the variable firstName in a trace( ) statement:

firstName = "Christine";
function getName ( ) {
  trace(firstName);
}
getName( );

When we invoke getName( ), the interpreter must find the value of firstName. There is no parameter or local variable called firstName in the local scope of getName( ), so the interpreter checks the timeline of clipA for the variable firstName. There, the interpreter finds firstName and displays its value, "Christine".

Our trace( ) statement is able to refer to a timeline variable from the body of our getName( ) function because getName( ) does not, itself, define a parameter or local variable called firstName. Now consider what happens when we add a parameter called firstName to the getName( ) function:

firstName = "Christine";
function getName (firstName) {
  trace(firstName);
}
getName("Kathy");

This time, when we invoke getName( ), we pass "Kathy" as the first (and only) argument, and the interpreter assigns the value "Kathy" to the parameter firstName. When the interpreter executes the trace( ) statement, it searches the local scope, where it finds the firstName parameter and its value "Kathy". So, the output of the function this time is "Kathy", not "Christine".

Even though the timeline variable firstName exists, the function's parameter called firstName takes precedence. The parameter is said to override or occlude the timeline variable.

Our example demonstrates the operation of the scope chain—the hierarchy of objects used by the interpreter to resolve references to variables. For functions attached to timelines, the scope chain has only three levels: the local function scope, the scope of the movie clip that bears the function declaration, and the global scope. (Flash 5 does not have a global scope.) But when we nest functions, the scope chain can involve many more levels, as we'll see later.

To access variables and properties that are not local to the function's scope, we must use dot syntax to form an explicit reference (also called a qualified reference). For example:

function dynamicGoto () {
  // Deliberately go outside the function's local scope
  _root.myClip.gotoAndPlay(_root.myClip.targetFrame);
}

Note that a function's scope chain is determined permanently at compile time, relative to the function declaration statement, not any function invocation statement. That is, the code in a function's body is scoped to the movie clip that bears the function declaration, not the movie clip that bears the statement that invokes the function. Scope of this type is called lexical or static scope (as opposed to dynamic scope, where the scope can change at runtime). We'll return to the topic of lexical scope when we examine nested functions.

Here's an example showing a misguided use of the scope chain:

// CODE ON MAIN MOVIE TIMELINE
// This function's scope chain includes the main movie
function rotate (degrees) {
  _rotation += degrees;
}

If we attempt to rotate clipA using _root.rotate, it rotates the entire main movie, not just clipA:

// CODE ON clipA's TIMELINE
_root.rotate(30);  // Oops! This rotates the entire movie!

In this situation, we can fix our problem by passing the clip to be rotated as an argument to the rotate( ) function:

function rotate (theClip, degrees) {
 theClip._rotation += degrees;
}
// From the _root timeline, invoke rotate( ) with a reference to clipA.
rotate(clipA, 30);

// Or, from clipA's timeline, invoke rotate( ) with a reference to clipA.
_root.rotate(_root.clipA, 30);

Alternatively, we can define the rotate( ) function as a method of the MovieClip class so that it has access to the this keyword, which refers to the movie clip object through which it was invoked:

MovieClip.prototype.rotate = function (degrees) {
  this._rotation += degrees;
}

// CODE ON clipA's TIMELINE
this.rotate(30);

Functions that operate on a movie clip are best defined as methods of the MovieClip class, as discussed in Chapter 12 under Section 12.8.4.

Due to a bug in Flash Player 5, function literals assigned as movie clip methods were not lexically scoped. For example:

// Create a variable on the current timeline.
var x = 10;
// Create a variable on theClip's timeline.
theClip.x = 20;
// Assign a function literal as a method of theClip.
theClip.traceX = function ( ) {
  // Flash Player 5 erroneously displays the value of x in theClip: 20
  // Flash Player 6 correctly displays the value of x on the timeline
  // that bears the function declaration: 10
  trace("x is: " + x);
}
// Invoke the traceX( ) method.
theClip.traceX( );

For the sake of backward compatibility, Flash Player 6 applies the correct scoping behavior only with Flash 6 format .swf files. The behavior of Flash 5 .swf files does not change when they are played in Flash Player 6.

9.7.2 Local Variables

Variables assigned to a function's local scope are called local variables. Local variables, including parameters, are accessible only to statements in the body of the function in which they are defined and exist only while that function runs. To create a local variable (other than the parameters that automatically become local variables), we use the var statement inside any function, like this:

function funcName () {
  var temp = "just testing!";  // Declares the local variable temp
}

Local variables are useful for holding information temporarily. Here, for example, we use the local variable lastSpacePlusOne to hold an interim result. Like all local variables, it dies when the function ends:

function getLastWord (text) {
  var lastSpacePlusOne = text.lastIndexOf(" ") + 1;               // Local
  var lastWord = text.subString(lastSpacePlusOne, text.length);   // Local
  return lastWord;
}
// Displays: "word"
trace(getLastWord("Tell me the last word"));
// Displays: undefined. lastSpacePlusOne is local and not
// available outside the getLastWord( ) function.
trace(lastSpacePlusOne);

When local variables expire at the end of a function, the memory associated with them is marked for automatic deletion. By using local variables to store all temporary values, we can conserve memory in a program. Furthermore, when we declare a local variable, we need not worry that it might conflict with a timeline variable of the same name.

Of course, not all variables used in functions need to be local. Earlier we saw that we can create, read, and set timeline variables from inside a function. Within a function, variable assignment statements that do not apply to a local variable are scoped to the timeline that bears the function declaration. In this example, x is a local variable, but y and z are timeline variables:

var z = 1;

function createVars () {
  var x = 10;    // Create local variable x
  y = 13;        // Create timeline variable y
  z = 2;         // Alter timeline variable z
}
createVars();    // Call the function
trace(x);        // x is undefined (x dies when the function ends)
trace(y);        // y is 13 (y exists after the function ends)
trace(z);        // z is 2 (z was permanently altered by the function)

The rules for creating timeline variables apply even if our function is a method of an object. We won't cover objects until Chapter 12, but for those familiar with object-oriented programming, Example 9-2 shows code that proves the point. Observe that x is defined on the timeline because it doesn't exist in the local scope of newFunc( ).

Example 9-2. Variable scope within object methods
newObj = new Object();                     // Create an object
newObj.newFunc = function () { x = 12; };  // Attach a new method
newObj.newFunc();                          // Call the method

// Now let's check for x
trace("x is " + x);                        // x is 12
trace("newObj.x is " + newObj.x);          // newObj.x is undefined

Using unqualified variable references inside functions to affect timeline variables may appear handy, but more often than not it is confusing, particularly in large projects. It's wise to take the ambiguity out of variable access by using qualified variable references (i.e., prefix the variable with an explicit object reference, such as this.x versus x, as discussed in Chapter 2 under Section 2.5.4).


Table of Contents