Table of Contents

9.10 Nested Functions

In ActionScript, functions can be declared within functions. Example 9-10 creates a function, showFullName( ), with two nested functions, showFirstName( ) and showLastName( ).

Example 9-10. A function with two nested functions
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:

http://www.crockford.com/javascript/private.html

9.10.1 Nested Function Scope

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:

And the scope chain of showFirstName( ) is

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.

Example 9-11. Remembering an interval ID
_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.

Example 9-12. Implementing a slideTo( ) method
// 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);
}

Table of Contents