1
votes

My background is heavy in JavaScript. I have a very advanced understanding of both ES5 and ES6. At work I was recently assigned a project involving an older flash application, which uses AS2. It is my understanding that ActionScript is very similar to ES5, but with classes and optional strict typing (akin to TypeScript and Flow), as well as a few other classic OO features. It is fairly straightforward so far, but I'm having trouble understanding how this and references work in ActionScript.

This is my understanding for JavaScript. this in a function can reference:

  • A bound variable, if using Function.bind() (as well as Function.call() and Function.apply()), which cannot be changed in the bound function, for example:

function func() {
    return this.number;
}

var bound = func.bind({ number: 2 });
console.log(bound()); // 2
  • An object, if the function is called as a method on that object, for example:

function func() {
    return this.number;
}

var obj = { number: 2, func: func };
console.log(obj.func()); // 2
  • An instance of a class, if that function is defined on the prototype of that class, for example:

function Class() {
    this.number = 2;
}
Class.prototype.func = function func() {
    return this.number;
}

console.log(new Class().func()); // 2
  • The global object, if the function is called without any kind of binding or object or instance attached to it, for example:

var number = 2;

function func() {
    return this.number;
}

console.log(func()); // 2

In ActionScript things seem to be a bit different. For one thing, you can access class members without this if you are doing it within a method of that class, similar to languages like C# and Java:

class MyClass {
    private var number:Number = 2;

    public function func():Number {
        return number;
    }
}

trace(new MyClass().func()); // 2

Also, the ActionScript standard library doesn't seem to have a Function.bind() method, though it does have Function.apply() and Function.call() which seem to work just like the JavaScript variations: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/2/help.html?content=00001072.html#265677. There also don't seem to be prototypes, which makes sense because classes are more abstract syntactic structures rather than functions (just like C#/Java) based on my understanding.

So my question is, excluding the lack of Function.bind() and Function.prototype, are the rules the same between ActionScript and JavaScript?

In addition, what happens if I do this:

class SomeClip extends MovieClip {
    private var childClip:MovieClip;
    private var number:Number = 2;

    public function SomeClip() {
        this.onLoad = function() {
            // SomeClip onLoad hander, `this` will be the SomeClip instance

            childClip._visible = true; // How is childClip resolved here?

            childClip.onRelease = function() {
                // childClip onRelease handler, `this` will be childClip

                trace(number); // How is number resolved here?
            };
        };
    }
}

Basically, if you access a member without this in an event handler, or some other loose function that is not a method of the class, what happens? I would guess that in the first case, it would resolve to this.childClip and work as one would expect, but in the second case, the resolution would fail because the onRelease handler's closure won't contain a reference to the SomeClip instance.

2
AS2 is somewhat like ES3. AS3 is based on the abandoned ES4 spec (rules were different in regards to classes and such). Neither have ES5+ features like Function.prototype.bind.Alexander O'Mara
this is in ES just refers to the current execution context which is determined by the current function/method, eval, and there is the global execution context which this refers to window.Andrew Li

2 Answers

2
votes

I see the comments that have been written so far are more focused on JS, so I'll try my best to answer from an ActionScript perspective.

In the world of AS2/AS3, functions that are defined as methods on a class have their this value bound to the class. This is typical of many higher-level languages with modern classes, such as Java, Haxe, etc. As such, in ActionScript you'll rarely find the need to use the this keyword other than cases where a variable name might be shadowed by a function argument:

public function Point(x:Number = 0, y:Number = 0)
{
    // A rare but necessary use-case of "this" in AS2/AS3
    this.x = x;
    this.y = y;
}

On the other hand, if the function you provide is anonymous as in the example you wrote, the behavior depends on whether or not you prepend this:

childClip.onRelease = function() {
    trace(number);
};

In this case ActionScript is able to determine number is a member of the class, and will print 2 like you expected since. This is because the interpreter looks for the next closest thing in the stack. In other words, you were ambiguous by excluding this so it knows it needs to perform a lookup.

However if you were to trace(this.number) instead, you would find that you get an undefined (and possibly even an error). This is because this is not a member variable on the class, and now points to a "global object" similar to JS. To avoid dancing with the global object, it's common practice for ActionScript developers to define all of their listeners as class instance methods:

class MyClass extends EventDispatcher
{
    private function MyClass()
    {
        addEventListener(Event.CHANGE, onChangeEvent);
    }
    private function onChangeEvent(e:Event) {
        trace(this); // refers to this class, and no need for bind() like JS
    }
}

Well organized AS3 code will almost never contain inline anonymous functions, since it's much easier to handle garbage collection by using explicit function references.

One last thing to note - you can expect functions that are methods of regular Objects in ActionScript to behave like JavaScript where passing them around via event listeners will result in the context of this being lost, and Flash will not do the magic lookup to locate the variable you referenced:

var obj = {
    func: function () {
        trace(this); // weird global object
    }
};
addEventListener(Event.CHANGE, obj.func);

Hope that helps!

2
votes

In AS2 functions are not bound and get "this" reference passed (evidently via Function.apply or by the object reference) in the moment of call:

function getIndex()
{
    trace(this.index);
}

var A = {index:1, getIndex:getIndex};
var B = {index:2, getIndex:getIndex};

A.getIndex(); // 1
B.getIndex(); // 2
B.getIndex.apply(A); // 1

Binding methods to certain objects was called "delegating": http://help.adobe.com/en_US/AS2LCR/Flash_10.0/help.html?content=00001842.html#1001423 In a nutshell, functions are objects too and you can create special function object that has references to both method to call and "this" object to pass:

function getIndex()
{
    trace(this.index);
}

function bind(method, target):Function
{
    var result:Function = function()
    {
        // arguments.callee is always a reference
        // to the current function object
        arguments.callee.method.apply(arguments.callee.target);
    }

    result.method = method;
    result.target = target;

    return result;
}

var A = {index:1};
var B = {index:2};

A.getIndex = bind(getIndex, A);
B.getIndex = bind(getIndex, B);

A.getIndex(); // 1
B.getIndex(); // 2
B.getIndex.apply(A); // 2

Then, if you don't use "this" reference, once you address some variable by its name there are several contexts that are searched for such a variable in order:

  • local function variables
  • local wrapper function variables (this one is truly horrible for no one really knows where these variables exist and it is a potent memory leak)
  • MovieClip, that holds the function code, local variables
  • global variables

Play with the following code, comment some "index" variables and you'll see it:

// Global variable.
_global.index = 6;

// MovieClip local variable.
var index = 5;

function wrap():Function
{
    // Wrapper function local variable.
    var index = 4;

    return function()
    {
        // Function local variable.
        var index = 3;
        trace(index);
    }
}

wrap()();