Hack 77 Clone an Object 
ActionScript copies objects by reference rather
than by value. Clone an object to create an independent copy rather
than a reference to the original.
In many situations, you will store
values as properties of a single object, instead of as separate
variables, because the first technique offers a cleaner, more
structured approach. However objects are not always treated the same
way as simple variables. Consider the following code:
var x = 5;
var y = x;
trace(y); // Displays: 5
x += 1;
trace(x); // Displays: 6
trace(y); // Displays: 5
We set x equal to 5, assign the value of
x to y, and then increment
x. After the example, the value of
x is 6 and the value of y is 5.
As expected, they are independent; changing the value of
x after the assignment statement
var y =
x; doesn't alter the value of
y. Therefore, variables that hold primitive values
such as numbers are said to be passed by
value because ActionScript copies the value
held in variable x at the time it is assigned to
the variable y.
Let's try the analogous operation with objects:
var obj1 = new Object( );
obj1.prop = 5;
var obj2 = obj1;
trace(obj2.prop); // Displays: 5
obj1.prop += 1;
trace(obj1.prop); // Displays: 6
trace(obj2.prop); // Displays: 6
This time, we create object obj1, add property
prop to it, and set obj2 equal
to obj1. Do we have two separate objects?
Immediately after the assignment, obj2.prop is 5,
as we'd expect. However, if we increment
obj1.prop and then trace the value of
obj1.prop and obj2.prop, we
find they are the same—they are both 6. Did you expect
obj2.prop to still be 5 even after
obj1.prop was set to 6?
The explanation is that we have not created two separate objects, but
two references to a single object (both obj1 and
obj2 hold a pointer to the same location of the
object's data in memory, not a primitive datum such
as the number 6).
Because both obj1.prop and
obj2.prop point to the same thing in memory,
changes made to the value via either reference are reflected in both.
This is akin to the way that you can drag a single symbol to the
Stage multiple times to create multiple movie clip instances. If you
change the Library symbol of the movie clip, all instances reflect
the change. (However, the analogy doesn't hold 100%.
Movie clip instances, although dependent on the Library symbol, are
independent of one another. Changes to the instance properties of one
clip are not reflected in other clips derived from the same Library
symbol.)
Complex datatypes such as objects and arrays are said to be
passed by
reference. Complex datatypes can be very large,
so passing them by reference is more efficient. For example, to
operate on an array, Flash need pass only a pointer to the array
rather than all the data within it.
This can be a problem, however, if you want the duplicate to be a
separate copy rather than a reference to the original. You could
clone an object by defining each property explicitly:
var obj1 = new Object( );
obj1.prop1 = 5;
obj1.prop2 = 12;
var obj2 = new Object( );
obj2 .prop1 = obj1.prop1;
obj2 .prop2 = obj1.prop2;
trace(obj2.prop1); // Displays: 5
obj1.prop1 += 1;
trace(obj1.prop1); // Displays: 6
trace(obj2.prop1); // Displays: 5 (not 6)
The two objects, obj1 and obj2,
and their properties, such as obj1.prop1 and
obj2.prop1, are now independent.
We can build our own method to create a unique copy of an object
rather than a reference to it, by adding the following
clone( ) method to the Object
class. This is ActionScript 1.0 code, but it will work in
ActionScript 2.0 as well.
Object.prototype.clone = function( ) {
if ( (typeof this) != "object" || (this instanceof Button) ||
(this instanceof TextField) ) {
// Return the original reference if this is a movie clip,
// button, or text field (i.e., don't clone these types).
return this;
}
var c = new Object( );
for (var n in this) {
var o = this[n];
if ( (typeof o) == "object" &&
!(o instanceof Button || o instanceof TextField)) {
// Property is itself an object, so clone it recursively
c[n] = o.clone( );
} else {
// Properties that are primitives are copied by value.
// Otherwise, it's a reference to a function, MovieClip,
// Button, or TextField, which we'll copy by reference
// because we can't easily create an onscreen representation.
c[n] = o;
}
}
return c;
}
// Keep clone instance method from showing up in for...in loops,
// and protect it from deletion:
ASSetPropFlags(Object.prototype,["clone"],5,1);
The preceding code adds a new Object.clone( )
method to the Object class. It also works with
most classes that inherit from Object, as all
ActionScript classes do. The preceding code does not, however,
attempt to clone movie clips, because the built-in
MovieClip.duplicateMovieClip(
) method can already create an
independent copy of a clip. Although you could use
duplicateMovieClip( ) within the preceding code
to clone nested clips, typically you wouldn't want
to duplicate a clip just because more than one object has a property
that refers to it. Similarly, you won't ordinarily
want to clone existing text fields or buttons.
You're more likely to use, say,
MovieClip.createTextField( ) to create a new
text field rather than clone an existing one.
Given the preceding definition of the clone( )
method, the following snippet creates an independent copy (i.e.,
clone) of obj1 and stores a reference to the new,
independent object in obj2:
obj2 = obj1.clone( );
Contrast the preceding with:
obj2 = obj1;
which simply makes obj2 store a reference to the
same object as obj1.
Notice that:
If the clone( ) method is invoked on one of the
graphical classes (a movie clip, button, or text field), it returns a
reference to the object instead of cloning it. If the original is a
primitive object, such as a number, it returns the value. Likewise, the clone( ) method creates references
rather than clones when an object property is a primitive or an
instance of one of the graphical classes. Only enumerable properties are attached to the cloned object.
Enumerable properties include those you would usually want to search
through within a for...in loop (such as custom
properties and methods created by the developer) but not preset
properties that define how the class works internally or ActionScript
methods. We use the undocumented ASSetPropFlags( ) [Hack #82] to protect our new
Object.clone( ) method from deletion and hide it
from for...in loops.
You can test the clone( ) method as follows:
// Create an object containing a nested object for testing
obj = {name:"test", num:10, bool:true,
childObj:{childName:"original child name", childNum:5}, root:_root};
// Clone the object
cloneOfObj = obj.clone( );
// Change a value in the new object
cloneOfObj.childObj.childName = "changed child name";
// Check to see if the two values are independent (they are)
trace("obj.childObj.childName = " + obj.childObj.childName);
trace("cloneOfObj.childObj.childName = " + cloneOfObj.childObj.childName);
The preceding code demonstrates how both the clone object and its
child object are independent of the original object or its child
object. The clone( ) method saves you from
having to copy properties and user-defined methods manually if you
want a new copy of an object.
Cloning for Motion Graphics
Call me old-fashioned, but I prefer to
look at data and its effects visually, so here's
another example in which you can see the difference between data that
has been cloned versus data copied by reference.
The following code creates an
sData ("sprite
data") object, which is used to control several
semi-transparent circular clips to produce a particle effect. The
effect works by creating many identical movie clips, each of which
has an independent instance of sData on its
timeline.
function animate( ):Void {
this.sData = sData.clone( )
this._x = this.sData.x;
this._y = this.sData.y;
this.lineStyle(10, 0x404040, 10);
this.moveTo(-0.5, 0);
this.lineTo(0, 0);
this.onEnterFrame = function( ) {
this.sData.x += Math.random( ) * this.sData.s - sData.s / 2;
this.sData.y += Math.random( ) * this.sData.s - sData.s / 2;
this._x = this.sData.x;
this._y = this.sData.y;
};
}
var sData:Object = new Object( );
sData.x = 275;
sData.y = 200;
sData.s = 4;
for (var i:Number = 0; i < 500; i++) {
var dot:MovieClip = this.createEmptyMovieClip("dot" + i,
this.getNextHighestDepth( ));
dot.onEnterFrame = animate;
}
Notice that the preceding code contains an example of a common and
proper use of copying a movie clip by reference. The
dot variable holds a reference to the movie clip
instance
doti.
It always points to the movie clip created within the loop
without making a clone of the clip.
Each clip is unique and maintains its independent properties, such as
size and position. The code creates a simple Brownian motion (random
particle movement) simulation, as seen in gases and in Figure 10-7. All 500 clips are independent.

If you change the first line in the animate( )
function so that it copies by reference instead of cloning:
this.sData = sData;
you get a totally different result, as shown in Figure 10-8.

In the independent example, each clip has its own
sData object with separate x and y coordinates, so
the dots move independently. In the nonindependent version, a single
sData object is used throughout (i.e., all the
local versions of this.sData contain a reference
back to the original, sData).
Therefore, changing the properties of sData via
one reference changes the properties of every
this.sData. Instead of a random distribution, you
get a constrained squiggle shape that dances around the Stage. All
the particles act as part of a single, larger graphic entity because
their controlling data is now related; they have lost their
individuality.
Final Thoughts
References to existing clips are often used in loops when
you'd rather avoid referring to separate clips by
separate names. For example, in the following loop, we want to refer
to the current movie clip that was just created with
createEmptyMovieClip( ). It is much easier to
refer to the new clip using the reference myClip
rather than write code that refers to the movie clips
(clip0 to clip9) by their
specific instance names.
for (i = 0; i < 10; i++) {
var myClip:MovieClip = this.createEmptyMovieClip("clip" + i, i);
// Code that uses current clip, myClip goes here
myClip._rotation = 50;
}
In some cases, using a reference is typically an error on the
programmer's part—she might want to create
independent objects instead; the Object.clone( )
method presented here allows you to easily create independent
objects. In motion graphics, swapping between independent (unique)
data and references allows you to create vastly different final
effects with very little effort.
—Grant Skinner
|