weird behavior of variables in anonymous functions? - actionscript-3

Could anyone explain why the trace result of the code below are "5,5,5,5,5" rather than "1,2,3,4,5" and how do you make the anonymous function refer the collect element in the array?( in this example, "var item" should be referring list[0],[1],[2],[3],[4]).
var list:Array=[1,2,3,4,5];
var funcs:Array=[];
for each(var item:int in list){
funcs.push( function(){
trace(item);
});
}
for each(var func:Function in funcs){
func();
}
trace result: 5,5,5,5,5

The problem with your code is that you create a closure which you access later.
For what you want to do you need to create multiple closures that can be later accessed.
var list:Array=[1,2,3,4,5];
var funcs:Array=[];
var closure_factory = function(index) {
return function() { trace(index); };
};
for each(var item:int in list){
funcs.push(closure_factory(item));
}
for each(var func:Function in funcs){
func();
}

This is the result of two things:
Function-level scope in AS3: the variable declaration inside for each(var item:int in list) is equivalent to declaring a var item:int at the beginning of your function (in your example, at the start of your code).
Anonymous functions are closures, which contain not only the code that you specify trace(item), but also the environment for that code to run in. Specifically, each of the anonymous functions created by your code knows that it should use the item variable (declared at function scope) for printing (via trace()).
So, what happens is that item gets assigned all the elements of list, and then retains the last value (which is 5). It exists (does not go out of scope), and when those anonymous functions fire, each of them looks at the same item and prints the same value.

Related

Access a AS3 instance dynamically by iterating with variables

I want to be able to access a instance on the stage dynamically by looping through an array containing Strings that describes the path.
private var clockKeeper:Array = new Array("LB.anim.clock.lbclock");
trace(stage.LB.anim.clock.lbclock.text);
for (var key in clockKeeper) {
trace(stage[clockKeeper[key]].text);
}
When i access it manually with the first trace statement, it works.
When i do it dynamically it seems like Flash tries to find an object named "LB.anim.clock.lbclock" not LB.anim....
How can i change this behaviour and make it work?
You should try splitting the "path" which should then consist of locally available names, and address each object in order. "Locally available names" means there should be stage.LB, and that object should have a property anim, etc etc.
function getObjectByPath(theRoot:DisplayObjectContainer,
thePath:String,separator:String='.'):DisplayObject
{
var current:DisplayObjectContainer=theRoot;
var splitPath:Array=thePath.split(separator);
while (splitPath.length>0) {
var named:DisplayObject = current.getChildByName(splitPath[0]);
var addressed:DisplayObject=current[splitPath[0]];
// either named or addressed should resolve! Otherwise panic
if (!addressed) addressed=named; else named=addressed;
if (!named) return null; // not found at some position
splitPath.shift();
if (splitPath.length==0) return named; // found, and last
current=named as DisplayObjectContainer;
if (!current) return null; // not a container in the middle of the list
}
// should never reach here, but if anything, just let em
return current;
}
This provides two ways of resolving the path, by name or by property name, and property name takes precedence. You should then typecast the result to proper type.
Yes, call this as follows:
trace((getObjectByPath(stage,clockKeeper[key]) as TextField).text);

Evaluate where a function call originated from

Okay so I have a function called changeHandler - it is called by several eventListeners in other functions. I want to write several if statements that evaluate the source of function call and change the dataProvider of my ComboBox depending on the originating function. Example: one of the many functions is called displayCarbs() and has an eventListener like so:
function displayCarbs(event:MouseEvent):void {
myComboBox.addEventListener(Event.CHANGE, changeHandler);
}
(I've removed all of the unnecessary code from the function above)
The if statement inside the changeHandler will look something like this:
if (****referring function = displayCarbs****) {
myComboBox2.dataProvider = new DataProvider(carbItems);
}
I've searched high and low for something that can achieve this, but I just don't have a good enough grasp of AS3 or vocabulary to describe what describe what I mean to get the answer from Google.
The simplest way I can think of... Couldn't you simply create a text string that updates to the name of function before going to changeHandler then in turn changeHandler can check string content and act accordingly..
public var referring_function:String;
function displayCarbs(event:MouseEvent):void
{
referring_function = "displayCarbs";
myComboBox.addEventListener(Event.CHANGE, changeHandler);
}
function displayCarbs(event:Event):void
{
if (referring_function == "displayCarbs")
{ myComboBox2.dataProvider = new DataProvider(carbItems); }
if (referring_function == "displayOthers")
{ myComboBox2.dataProvider = new DataProvider(otherItems); }
// etc etc
}
I cant remember right now if you need == or just = when checking the If statement against strings.
I know there is an accepted answer already, but based on what I gleaned about the problem, here is a solution that wouldn't require adding another variable to check :
function displayCarbs(event:MouseEvent):void
{
myComboBox.addEventListener(Event.CHANGE, changeHandler);
}
function changeHandler(event:Event):void
{
var comboBox:ComboBox = event.target as ComboBox;
if (comboBox.dataProvider == uniqueProvider)
{
myComboBox2.dataProvider = new DataProvider(appropriateItems);
}
}
This should work if the second dataProvider is determined based on the first dataProvider. This of course requires that your uniqueProvider is a class member variable so it has scope within the handler.

How to pass parameters to external function through map.event.addListener

I want to add listeners to google map events, but not using anonymous functions but named, external functions as this happens inside a loop, I do not want to define an anonymous function right there, but instead use a named, external function:
Not:
for (...) {
googleMap.event.addListener(instance, eventName, function() {...});
}
But rather sth. like:
doSomething = function(parameter1, parameter2...) {
...
}
for (...) {
googleMap.event.addListener(instance, eventName, params, doSomething);
}
When "instance" is a google map marker, I can add the parameter(s) to the marker using marker.set(paramName, paramValue) and then access the parameters inside the event handler function via this.paramName, but is there any other way to pass values to the event handler function when I don't want to use an anonymous one?
Any advice welcome, Roman.
I had the same problem. Here is a solution. It genuinely avoids the create function in a loop problem, by using the pattern described here
In JavaScript, what are specific reasons why creating functions within a loop can be computationally wasteful?
Which I call the "function factory" pattern.
The other ingredients are that inside the function "this" refers to the object which raised the function (the thing on the map which was clicked, or whatever), and because JavaScript is completely dynamic, you can attach additional properties at will to the thing which was clicked, and query them by calling this.blah inside the function
function doSomethingHandlerFactory(){
var f = function(event){
//do something, for example call a method on a property we attached to the object which raised the event
this.blah.clicked(event);
};
return f;
}
//add a property to the google overlay object (for example a polyline which we've already set up)
thePolyline.blah = ...;
//get a handle for a function, attach the event (in this case to a polyline), and keep
//a reference to the event (in case we want to call removeListener later). The latter
//is optional.
var f = doSomethingHandlerFactory();
var ev = google.maps.event.addListener(thePolyline, 'click', f);
I hope this helps someone out.
How about wrapping your named function in an anonymous function:
google.maps.event.addListener(instance, eventName, function() { doSomething(parameter1, parameter2,...) });

ActionScript 3 isn't supposed to be a simple synchronous architecture language?

A simple piece of code that should trace :
rien
test
done!
and I get something completely far away from that,
scenario A :
var __functions_to_execute:Array;
function start():void {
__functions_to_execute =[];
__functions_to_execute.push(futile_trace());
__functions_to_execute.push(futile_trace('test'));
execute_functions();
}
function execute_functions():void {
if(__functions_to_execute.length){
//where shift on this Array remove the first element and returns it
var exec:Function =__functions_to_execute.shift();
exec;
//I tried this too, just in case
//__functions_to_execute[0];
//__functions_to_execute.shift();
} else trace("done!");
}
function futile_trace(_value:String ='rien'):void {
trace(_value);
execute_functions();
}
start();
pretty simple. but the result is :
rien
done!
test
lets add a deprecated function to this and lets change the futile_trace function to :
function futile_trace(_value:String ='rien'):void {
trace(_value);
setTimeout(execute_functions, 0);
}
and then the result is :
rien
test
done!
Ok then, I said to myself, why not, lets change the scope when I call execute_functions, so I tried :
function futile_trace(_value:String ='rien'):void {
trace(_value);
extra_step();
}
function extra_step():void {
execute_functions();
}
guess what was the result?! yeah :
rien
done!
test
so?! Is the trace function that bad? that slow? is it the fact that passing an argument to the function take so much time compare to the other one? I mean... wow!
is there something I can do to avoid this type of weirdness ?
(For the record, my project is not to trace {rien, done and test}... I have 15k lines of codes that react completely differently if I compile them with "Omit trace statements" or not.
Thanks for your input guys.
You are executing the functions and adding their return values to the __functions_to_execute array, not the functions themselves.
Your function execute_functions doesn't actually do anything. I've tried to explain the sequence in-line:
function start():void {
__functions_to_execute =[];
// 1. traces 'rien' first because futile_trace() is called with no args
// 2. 'done!' will be traced inside execute_functions because the array is still empty
// 3.undefined will be pushed into the array next
__functions_to_execute.push(futile_trace());
// 4. traces 'test'
// execute_functions does not trace anything because __functions_to_execute is non-empty
// but it also doesn't do anything because it is just removing the `undefined` value from the start of the array.
__functions_to_execute.push(futile_trace('test'));
execute_functions();
}
Something more like this should behave how you expect. It's storing in the array function references, along with the arguments that should be passed when the function is called.
var __functions_to_execute:Array;
function start():void {
__functions_to_execute = [];
__functions_to_execute.push({func:futile_trace, args:[]});
__functions_to_execute.push({func:futile_trace, args:['test']});
execute_functions();
}
function execute_functions():void {
if(__functions_to_execute.length){
var obj:Object = __functions_to_execute.shift();
obj.func.apply(null, obj.args);
} else trace("done!");
}
function futile_trace(_value:String ='rien'):void {
trace(_value);
execute_functions();
}
start();
For scenario A, you're not actually ever pushing futile_trace to the array - you're calling it (notice the () after the function name), and then pushing the result of that call to the array.
In other words:
You call futile_trace()
futile_trace traces 'rien', because you passed no value.
futile_trace calls _execute_functions
At this point, nothing has been pushed yet, so _execute_functions traces 'done!'
_execute_functions returns.
_futile_trace returns.
The result of futile_trace() (void) is pushed.
You call futile_trace('test')
futile_trace() outputs 'test'.
futile_trace calls _execute_functions
_execute_functions shifts void from the array.
_execute_functions executes void; (which does nothing)
etc. etc.
If you need to pass a function to another function or store a reference to it in a variable, make sure you're not calling it.
__functions_to_execute.push(futile_trace);
// Use an anonymous function to pass with arguments without executing:
__functions_to_execute.push(function() { futile_trace('test'); });
... and in _execute_functions do remember the parantheses:
exec();

how to pass this (MovieClip) as a parameter?

How can I pass the keyword this OR an instance name as a parameter inside a function?
function (reference:InstanceName):void // kind of what I want
{
reference.gotoAndPlay("frameLabel");
}
To clarify jozzeh's correct answer: your problem is variable scope. The "this" keyword's scope is contained to the owning object - you would need to establish the proper scope of the parent timeline in your function call:
function goTo( reference:MovieClip ):void
{
reference.gotoAndPlay("Start");
}
goTo(this.root); // variable scope of "this" is now at the class level
Obviously, we sometimes need parameter initializers, but in this case - a reference to 'this' is going to throw an error. If this is a function that has a changing value, sometimes the focus of which is of its own root, you'd need to handle the initializing logic outside of the method sig.
good luck!
I have never seen this being referenced like that...
The 'this' within a function is a reference to the object that initialized the function.
example:
var test:String = "testing the test."
test.myfunction();
function myfunction():void{
//this = the variable "test"
this.indexOf('test');
}
Also if you want to pass a variabel to a function it should be like this:
var test:String = "testing the test."
myfunction(test);
function myfunction(mystring:String):void{
var indexer:Number = mystring.indexOf('test');
}