In ActionScript, functions can be declared within functions. Example 9-10 creates a function, showFullName( ), with two nested functions, showFirstName( ) and showLastName( ).
function showFullName ( ) { function showFirstName ( ) { trace("Colin"); } function showLastName ( ) { trace("Moock"); } showFirstName( ); showLastName( ); } showFullName( );
Inside showFullName( ), the nested functions can be invoked as usual. However, like a local variable, a nested function is accessible only to its parent function. Code outside the parent function cannot execute the nested function.
Nested functions are often used to encapsulate a series of complex operations into a single package, while preventing the outside world from gaining access to its internal workings. For an interesting example of nested functions (sometimes called inner functions) used to simulate Java-style private members, see:
When a nested function executes, the interpreter adds the nested function's local scope to the scope chain of its parent function. For example, in Example 9-10 the scope chain of showFullName( ) is:
Global object (interpreter ends its search for variables here)
Enclosing movie clip object (where showFullName( ) was defined)
showFullName( ) local scope (interpreter starts its search for variables here)
And the scope chain of showFirstName( ) is
Global object (interpreter ends its search for variables here)
Enclosing movie clip object (where showFullName( ) was defined)
showFullName( ) local scope
showFirstName( ) local scope (interpreter starts its search for variables here)
The scope of the nested function is the same as its parent's with the addition of its own local scope. If a variable does not exist in showFirstName( )'s local scope, the interpreter will check showFullName( )'s local scope, and so on up the chain. Furthermore, all variables defined in an outer function are available to nested functions, even after the outer function finishes executing. In the following example, the function enableZoom( ) creates an onMouseDown( ) event handler on theClip, using a nested function literal.
function enableZoom (theClip) { var zoomFactor = 1.5; theClip.onMouseDown = function ( ) { this._xscale *= zoomFactor; this._yscale *= zoomFactor; } } // Enable zooming for the current movie clip. enableZoom(this);
The onMouseDown( ) handler will execute whenever the mouse is clicked—long after enableZoom( ) has finished its own execution. However, even though enableZoom( ) has finished executing, its zoomFactor variable continues to be available to the nested onMouseDown( ) function via the scope chain.
Here are two more advanced examples showing the power of nested function scope.
Example 9-11 creates a new global function, setTimeout( ), that invokes a supplied function, func, after delay milliseconds. A nested function, callFunc( ), does the work of invoking func (which is passed as a parameter to setTimeout( )). Via the scope chain, callFunc( ) can refer to both setTimeout( )'s func parameter and its local id variable.
_global.setTimeout = function (func, delay) { var id = setInterval(callFunc, delay); function callFunc ( ) { clearInterval(id); func( ); } } // Usage: setTimeout(sayHi, 1000); function sayHi ( ) { trace("hello world"); }
Example 9-12 is a variation on the same theme. It uses the scope chain to access the parameters of slideTo( ) from the nested function assigned to the calling movie clip's onEnterFrame( ) event handler.
// Method: MovieClip.slideTo( ) // Desc: Animates a clip to the point (leaderX, leaderY) at // the specified speed. // Params: leaderX The x-coordinate to move to. // leaderY The y-coordinate to move to. // speed Rate of movement (pixels per frame). MovieClip.prototype.slideTo = function (leaderX, leaderY, speed) { // Only move if the clip's not at the destination. if (this._x != leaderX && this._y != leaderY) { // Create a nested function and assign it as the clip's // onEnterFrame( ) event handler. this.onEnterFrame = function ( ) { // Determine distance between clip and destination. // The parameters leaderX and leaderY are accessed via // the scope chain. var deltaX = this._x - leaderX; var deltaY = this._y - leaderY; var dist = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); // Allocate speed between x and y axes. var moveX = speed * (deltaX / dist); var moveY = speed * (deltaY / dist); // If the clip has enough speed to overshoot the destination, // just go to the destination. Otherwise, move according to // the speed specified by the slideTo( ) parameter. if (speed >= dist) { this._x = leaderX; this._y = leaderY; // The clip has arrived, so stop remove onEnterFrame( ). delete this.onEnterFrame; } else { this._x -= moveX; this._y -= moveY; } } } } // Example Usage: // Slides the clip to the mouse position when the mouse is clicked. theClip_mc.onMouseDown = function ( ) { this.slideTo(_root._xmouse, _root._ymouse, 10); }