Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

For our implementation, we have to take into account that in a browser, code loading cannot be done synchronously "on demand". Thus we use AMD and map compilation units to AMD modules. The AMD loader resolves all static dependencies and loads the code of all compilation units that are potentially needed at runtime. We have to take care of initializing those compilation units only when they are actually used. This determines the value of a compilation unit the corresponding AMD module returns: Instead of the primary declaration itself, the return value is a compilation unit object from which you can retrieve the primary declaration. The trick is that the compilation unit self-initializes when the primary declaration is first requested!.

We could implement the primary declaration request by a method of the compilation unit object. But this would involve a function call for each retrieval of the primary declaration, even if the compilation has already been initialized. A trick for an efficient implementation is to use a get property that, upon its first invocation, initializes the compilation unit and replaces itself by a simple field with a direct reference to the primary declaration. For the sake of brevity and readability (we want the reader to mainly ignore this helper property), let's call that property _ (underscore).

...

This is not only a performance / memory consumption problem, as in AS3, the method keeps is its identity. This is important for the typical use case that bound methods are registered as event listener callbacks. When trying to remove an event listener, identity has to be ensured, or the function will not be found and the event listener is not removed.

...

"this" is always in scope

TODO

Statements

Almost all statements can be mapped one-to-one from ActionScript to JavaScript.
There are only two exceptions: for each and try... catch.

for each

In JavaScript, you always have to write this.member explicitly.
In ActionScript, this is automatically in scope in every non-static method.
This could be simulated by enclosing the method body in a with(this) block, but this solution is discouraged because with is considered evil, not supported in strict mode, prevents optimizations, and the scope sequence of parameters would be wrong.

Complement "this."

Instead, the compiler has to take care to complement "this." before non-static class members. Note that class members may be hidden by local variables and parameters, so "this." must not be complemented in such cases.

ActionScript:

Code Block

public class MyClass {
  public var id:String;
  public function MyClass(id:String) {
    this.id = id;
  }
  public function toString():String {
    return id;
  }
}

JavaScript:

Code Block

function MyClass(id) {
  this.id = id;
}
Object.defineProperties(MyClass.prototype, {
  id: { value: null, writable: true },
  toString: {
    value: function() {
      return this.id;
    }
  }
});

Note that the second occurrence of id in the constructor body is not prefixed by "this.", as it refers to the parameter.

"this" inside functions

An occurrence of this inside a function (in contrast to a method) does not refer to the "outer" this defined by the class context:

ActionScript:

Code Block

public class MyClass {
  public var prefix:String;
  public function MyClass(prefix:String) {
    this.prefix = prefix;
  }
  public function prefixAll(values:Array):Array {
    return values.map(function(s:String):String {
      return this.prefix + s; // "this" is not defined here as intended!
    });
  }
}

This code does not work as intended, because this inside functions is bound dynamically.
You can solve this problem by either using the second optional parameter thisObject of Array#map(), or by simply removing the this. qualifier from prefix. Let's do the latter:

Code Block

  public function prefixAll(values:Array):Array {
    return values.map(function(s:String):String {
      return prefix + s;
    });
  }

This works, because the anonymous function inside prefixAll is also in lexical scope of the outer this. Unfortunately, this fact is hidden at runtime, because the local this of the anonymous function hides the outer this. Thus, to be able to access the outer this in the generated JS code, we have to alias it, say to this$:

JavaScript:

Code Block

function MyClass(prefix) {
  this.prefix = prefix;
}
Object.defineProperties(MyClass.prototype, {
  "prefix": { value: null, writable: true },
  "prefixAll": {
    value: function(values) {
      var this$ = this;
      return values.map(function(s) {
        return this$.prefix + s;
      });
    }
  }
});

This aliasing should be done at most once right at the start of the method only if it contains at least one function that needs an outer this reference.

Statements

Almost all statements can be mapped one-to-one from ActionScript to JavaScript.
There are only two exceptions: for each and try... catch.

for each

Unlike the for... in loop, the for each... in Unlike the for... in loop, the for each... in loop has been introduced to JavaScript later, so to make sure the generated code runs in JavaScript 1.5 browsers, we have to simulate it.

...

Note that in case no type check matches, the error has to be re-thrown! . This code can be omitted if the original code contains a catch clause with an untyped or * typed error variable.

...

Code Block
var $0, $1;
($0 = <exp1>)[$1 = <exp2>] = $0[$1] && <rhs>

Wiki MarkupNote that {{foo().y}} is the same as {{foo()\['y'\]}} and thus also matches this pattern.

Examples

Simple lhs

ActionScript:

...