What does var
actually do, and why doesn't setting myObject = null
actually remove the object? These questions tie in to a fundamental concept in coding, relevant whether your language of choice is AS3, JavaScript, or C#, and can be understood using a few common items from the stationery cupboard.
What's a Variable?
Let's start with the basics. Suppose you want to store the age of your friend Bill:
var ageOfBill:Number = 24;
(I'm going to use AS3 for these example, but the basic concepts are the same in JavaScript and C#.
In JavaScript, the syntax is almost the same, but we don't specify that age is a number:
var ageOfBill = 24;
In C# we don't use the var
keyword, but we do specify the type of the variable:
short ageOfBill = 24;
Not different enough to be confusing, I hope.)
So what's happening here? Think of it this way:
-
var
(orshort
, in C#) means, "get a fresh Post-it note". -
ageOfBill
means, "writeageOfBill
across the top, in pen". -
= 24
means, "write24
on the note, in pencil".
What if later we realise that Bill's actually younger than we thought?
var ageOfBill = 24; //...later... ageOfBill = 20;
This just means we find our ageOfBill
note, erase 24
, and write 20
on it instead.
We could write var
again:
var ageOfBill:Number = 24; //...later... var ageOfBill:Number = 20;
...but this is not good code, because var
says, "get a fresh Post-it note". If you do this, the compiler will usually figure out what you mean - i.e. that you want to change what's written on the existing ageOfBill
Post-it note rather than actually getting a fresh one - but it will probably complain.
Warning: #3596: Duplicate variable definition.
It depends on the language and on your coding environment.
So can we ask the compiler to get a fresh Post-it note and write a label on it in pen, without writing anything on it in pencil? Perhaps we could do this for Bill's friend Marty, whose age we don't know:
var ageOfMarty:Number;
Actually (in AS3, at least) this will get a fresh Post-it note, write ageOfMarty
across the top, in pen... and then write a default initial value of 0
on there in pencil:
So, in other words, we can't have a Post-it note like this without it taking some value.
Okay - what about if we want to store the age of Bill's best friend Ted, who we know is the same age?
var ageOfTed:Number = ageOfBill;
What happens here is, the computer looks at the ageOfBill
Post-it, then copies the number written on it in pencil to a fresh Post-it, on which it writes ageOfTed
across the top in pen.
This is just a copy, though; if we then change the value of ageOfBill
it won't affect ageOfTed
:
ageOfBill = 21;
So! That's all pretty straightforward, and maybe even intuitive. Now let's talk about the first common pain point: arrays.
What's an Array?
Think of an array as a ring binder.
(I was going to say a rolodex...
...but I realised that I had never even seen one in real life.)
Each sheet inside the binder is like one of those Post-it notes, except without the pen-written label across the top. Instead, we refer to each sheet by the name of the binder and the page number of the sheet.
Let's suppose we've got an array of all our friends, in no particular order. Who's on the first page (page #0)?
trace(friends[0]);
(trace()
just writes the line to the debug output; in JavaScript, you might use console.log()
and in C# you might use Console.WriteLine()
for the same purpose.)
It's Bill!
So, now, what does the following line do?
var firstFriend:String = friends[0];
It gets a fresh Post-it note (because of the var
keyword), writes firstFriend
across the top in pen, then copies whatever's written on the first page of the binder onto that note in pencil.
(Remember, String
just means a piece of text.)
We can overwrite what's written on any page of the binder, just like with the Post-it notes:
friends[0] = "Kyle";
...and of course this doesn't affect the firstFriend
Post-it.
The binder is an apt analogy, because - just like with an array - you take take pages out, add new ones, and rearrange them. But remember, individual pages act just like the Post-it notes, except they don't have their own pen labels, just page numbers.
Still pretty straightforward, I hope. So here's an interesting question: what happens when you do the following?
var listOfNames:Array = friends;
Uh...
You Can't Write That on a Post-it
I've cheated a bit here, because I talked a bunch about arrays without ever explaining how we create one in the first place. So let's tackle that now.
Suppose you type:
var friends:Array = ["Bill", "Marty", "Ted"];
...What happens?
Well, as usual, var friends
means we get a fresh Post-it note and write friends
across the top, in pen:
But what do we write on it in pencil?
It's a trick question: we don't write anything.
See, Array
means, "get a new ring binder". And ["Bill", "Marty", "Ted"]
means "put three pages in the binder, with these names on it":
You can't see the "Marty" and "Ted" pages, but they're totally there.
And then? It's simple! We stick the friends
Post-it note to the cover of the binder:
Now, when we write:
trace(friends[0]);
...we know that we have to find the Post-it labelled friends
, then look at whatever's written on the first page (page #0) of the binder that it's stuck to.
There are actually very few types of variable where a value gets written onto a Post-it note in pencil. In AS3, the only such types (called "primitives") are:
Number
String
int
uint
Boolean
For everything else - Object
, MovieClip
, XML
, and so on - we stick the Post-it note onto the item itself.
(The details are a little different in JavaScript and C#, but overall the same idea applies.)
So let's get back to our earlier question. When we type:
var listOfNames:Array = friends;
...what happens?
Again, we know that var listOfNames
means "get a fresh Post-it note and write listOfNames
across the top in pen". And now we know that Array
means we'll be sticking the Post-it note to something (a binder), rather than writing something on the Post-it in pencil.
Previously, when we've done something similar, we've copied the contents of one Post-it note onto another. So here, should we get a fresh new binder and copy all of the pages from the friends
binder into it?
Actually, no! All we do is stick this new listOfNames
Post-it note onto the same binder as the friends
Post-it note.
Now, friends
and listOfNames
each refer to the exact same array. So if we write:
listOfNames[0] = "Emmett";
...then friends[0]
will also be Emmett
, because listOfNames[0]
and friends[0]
refer to the exact same page in the exact same binder! And because that page only contains a String (which is a "primitive" type, remember), then we've just erased whatever was written on that page previously and written Emmett
there instead.
So What Does null
Mean?
Seen like this, null
is quite easy to understand. This statement:
friends = null;
...just means, "remove the friends
Post-it note from whatever it's currently stuck to".
The friends
Post-it still exists, it's just not stuck to anything. So if you type:
trace(friends[0]);
or
friends[0] = "Henry";
...then you'll get an error, because you're trying to reference the first page of the binder that the friends
Post-it is stuck to - but it's not stuck to anything!
So, to be clear, setting friends = null
doesn't affect the binder at all. You can still access it just fine via listOfNames
. And you can even type:
friends = listOfNames;
...to go right back to the old situation:
Garbage Collection
Like I said, setting friends = null
doesn't affect the binder directly, but it can have an indirect effect.
See, if there are no Post-it notes stuck to the binder at all, then there's no way for anyone to access the binder ever again. It'll just lie around, totally inaccessible. But having all these binders (and other objects) lying around, totally abandoned, is a real waste of space - they're cluttering up the computer memory.
That's where the garbage collector comes in. This is a tool that periodically checks for any "lost" objects, and throws them in the trash - and once they're gone, they're gone for good; if an array is garbage collected then all of its pages are, too.
For most practical purposes, this doesn't affect you at all; objects only get garbage collected if they're lost and unable to be found by your code. If you have a lot of these lying around, then you might notice a slight lag every now and then, when the garbage collector does its job (it takes a little time to actively collect the garbage). The benefit is that this clears up more room (memory) for your app.
(If you want to know more about this topic, read Daniel Sidhion's posts on garbage collection and object pooling.)
Arrays of Objects
Okay, there's one more big concept to grasp - and it's the most complex one yet.
Consider this snippet:
var firstFriendDetails:Array = ["Bill", 20]; var secondFriendDetails:Array = ["Marty", 16]; var thirdFriendDetails:Array = ["Ted", 20]; var allFriends:Array = [firstFriendDetails, secondFriendDetails, thirdFriendDetails];
How the heck does that work?
Let's start with what we know. The first three lines are easy; for each one we:
- Get a fresh binder.
- Insert a page with the friend's name written on it in pencil.
- For nsert another page with the friend's age written on it in pencil.
- Get a fresh Post-it and write the appropriate label across the top in pen.
- Stick the Post-it to the binder.
As for this line:
var allFriends:Array = [firstFriendDetails, secondFriendDetails, thirdFriendDetails];
...we're going to need some string and some tape.
We can think of that one line as being equivalent to this snippet:
var allFriends:Array = []; allFriends[0] = firstFriendDetails; allFriends[1] = secondFriendDetails; allFriends[2] = thirdFriendDetails;
The first line is easy: get a fresh binder and a fresh Post-it note, write allFriends
on the Post-it note, and stick it to the binder.
As for the second line:
allFriends[0] = firstFriendDetails;
Remember that I said that each page in a binder is like a Post-it note, except without anything written in pen. If the first page was a Post-it, then we'd simply stick it to the front of the firstFriendDetails
binder, right?
...but it can't be both on the front of that binder and inside the other binder. So, instead, we use string:
Same for the other two:
So when we want to know what allFriends[2]
refers to, we just open the allFriends
binder to that page and follow the string - which, of course, leads to the thirdFriendDetails
binder.
Similarly, for allFriends[1][0]
, we first figure out which binder allFriends[1]
refers to, and then we look at the first page of that binder... so allFriends[1][0]
is Marty
!
Loops
Now put all that information together, and bear it in mind when reading this snippet:
var friends:Array = ["Bill", "Marty", "Ted", "Emmett", "Henry"]; var currentIndex:int = 0; var currentFriend:String; while (currentIndex < 5) { currentFriend = friends[currentIndex]; trace(currentFriend); } var lastFriend:String = currentFriend; trace(lastFriend);
What if we modify the value of currentFriend
inside the loop?
var friends:Array = ["Bill", "Marty", "Ted", "Emmett", "Henry"]; var currentIndex:int = 0; var currentFriend:String; while (currentIndex < 5) { currentFriend = friends[currentIndex]; currentFriend = "Herbert"; trace(currentFriend); } trace(friends[0]);
What if the array contains non-primitive objects (MovieClips, images, arrays, 3D objects, whatever)?
var friends:Array = [firstFriend, secondFriend, thirdFriend, fourthFriend, fifthFriend]; var currentIndex:int = 0; var currentFriend:MovieClip; //or ":Image" or ":Object" or ":Array" or whatever while (currentIndex < 5) { currentFriend = friends[currentIndex]; currentFriend = sixthFriend; } //What is the value of friends[0] now?
Finally, what if the array contains other arrays, which themselves contain primitives?
var firstFriendDetails:Array = ["Bill", 20]; var secondFriendDetails:Array = ["Marty", 16]; var thirdFriendDetails:Array = ["Ted", 20]; var fourthFriendDetails:Array = ["Emmett", 50]; var fifthFriendDetails:Array = ["Henry", 36]; var friends:Array = [firstFriendDetails, secondFriendDetails, thirdFriendDetails, fourthFriendDetails, fifthFriendDetails]; var currentIndex:int = 0; var currentFriend:Array; while (currentIndex < 5) { currentFriend = friends[currentIndex]; currentFriend[0] = "John"; currentFriend = ["Herbert", 29]; }
What do you think the value of friends[3][0]
will be after that?
Other Bits and Pieces
Here are a few other important notes you should know:
Objects
In AS3 and JavaScript, Objects are like Arrays except each page is referred to by a label rather than by its page number. So you can type:
var detailsOfBill:Object = {}; //"{}" means "create an Object" detailsOfBill.title = "Esquire"; detailsOfBill.bandName = "Wyld Stallyns"; detailsOfBill.allNames = ["Bill", "S.", "Preston"];
...and this is kind of like getting a fresh binder, sticking a detailsOfBill
Post-it on the front, and filling it with three pages. The first page has the label title
written across the top in pen and the word Esquire
written on it in pencil; the second page has the label bandName
in pen and Wyld Stallyns
in pencil. The third page has the label allNames
but has nothing written on it in pencil; instead, a string attaches it to another, regular binder, whose pages are not labelled: the first page says Bill
, the second says S.
, and the third says Preston
, all in pencil.
(To make things even more confusing, arrays are technically a special form of Object. And if you think that's bad, functions can be seen as a type of Object, too! But that's a topic for a future article...)
More on Garbage Collection
I said that objects are garbage collected if they don't have any Post-it notes stuck to them, but this is an oversimplification. If one page of a binder points to an object via string (i.e. if myArray[0] = myObject
or similar), then that object won't be garbage collected. Same goes for if a page of a binder points to another binder (array), or if the page of an object binder points to another object binder... and so on. It even applies if the only way to access the object is through a Post-it stuck to a binder which has one page that's tied to another binder, as many layers deep as you want to go.
Basically, the garbage collector only collects an item if it can't be reached through any other variable.
This explains why objects that are on the screen usually can't be object collected. In AS3, if a MovieClip or other type of DisplayObject is in the display list, then it's automatically added to what is essentially a hidden array of display objects (which you can access via getChildAt()). Similar structures exist in JavaScript and C#. So if a graphic is on the screen, and you remove all references to it from variables, arrays, and objects, it still won't be garbage collected until you remove it from the display list.
Any Questions?
I hope this helps to clear things up. It's certainly a confusing concept when you first come across it: some variables actually contain a value, while others just contain a reference to an object. Don't worry if you're not 100% sure exactly what's going on; it'll make a lot more sense after a bit of practice (and a few mistakes!).
However, if you have any specific questions about it, just stick a comment below and I'll do my best to answer.
Thanks for reading!
Comments