Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • Private methods (non-static as well as static ones) and private static fields are declared in a new scope, shared by all class members. Thus, they are visible for every method, but not from outside. Non-static private method calls have to be rewritten to run with the correct this.
  • Private non-static fields and internal members are renamed: they are post-fixed by $ plus the inheritance level of the declaring class, so that they cannot name-clash with fields of the same name, declared in a subclass or superclass. The inheritance level is a number starting at 0 for Object, continuing with 1 for each direct subclass of Object, then 2 for classes inheriting from these classes, and so on. So for example a private field foo in a class extending Object would be renamed to foo$1. Internal members are also renamed, because they could name-clash with members of the same name, declared in a subclass or superclass residing in another package.

...

Packages

In ActionScript, a class may not only contain static members, but also static code. Static fields may also have initializers which are quite similar to static code.
Static code and static initializers are executed exactly once when the class is created. A class is created right before code that references it is executed. Note that this has nothing to do with import directives, nor does a class trigger immediate initialization of all classes to which it has static dependencies.

No matter how class creation is triggered, it is always executed as a whole:

  1. first, its superclass is created (which creates its superclass transitively)
  2. then, all static initializers are evaluated, in the order in which they appear in the source code
  3. last, all static code statements are executed, in the order in which they appear in the source code

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 to resolve all static dependencies and load the code of all classes that are potentially needed at runtime, but create those classes only right before they are actually used.
This changes what our AMD modules return: Instead of the class / constructor function, the return value is a class definition that still has to be initialized to create the actual class.

A class definition is a factory object for a class. A trick for an efficient implementation of the factory method is to use a get property that, upon its first invocation, can be replaced by a simple field with a direct reference to the (now initialized) class. For the sake of brevity and readability (we want the reader to mainly ignore this helper property), let's call that property _ (underscore).

Using this approach, a reference to another class like

Code Block

import com.acme.OtherClass;

public class ThisClass {
  public function ThisClass() {
    OtherClass.doSomething();
  }
  ...
}

would in generated JavaScript become

Code Block

define(["com/acme/OtherClass"], function(OtherClass_) {
  return Object.defineProperty({}, "_", {
    configurable: true,
    get: function() {
      function ThisClass() {
        OtherClass_._.doSomething();
      }
      Object.defineProperty(this, "_", { value: ThisClass });
      ...
      return ThisClass;
    }
  });
});

Note how the definition of ThisClass, instead of directly returning its constructor function, returns an object with a get property _, and how its implementation replaces that property by a read-only reference to the constructor (that's why it has to be configurable).
Also note that we use OtherClass_ (postfixed by an underscore!) to be able to use the original name OtherClass for a local variable as a shortcut to the class when we know it has already been initialized, like so:

Code Block

define(["com/acme/OtherClass"], function(OtherClass_) {
  return Object.defineProperty({}, "_", {
    configurable: true,
    get: function() {
      var OtherClass;
      function ThisClass() {
        (OtherClass = OtherClass_._).doSomething();
        OtherClass.doSomethingElse();
        var x = OtherClass.CONSTANT;
        ...
      }
      Object.defineProperty(this, "_", { value: ThisClass });
      ...
      return ThisClass;
    }
  });
});

Since for the time being, we still target Internet Explorer 8 (or even 7), we need a solution that also works without get properties.
In order not to always need to perform a function call (which results in a runtime penalty and is distracting during debugging), we try to read the field first and only call the factory function in case the property is not yet defined. Following the general pattern of naming explicit get functions (see below), the factory function for IE8 would be called get$_, resulting in the following expression to access a potentially not-yet-created class: (OtherClass_._ || OtherClass_.get$_())

Granted, simulating lazy class creation makes the solution a bit more complex, but it is an important AS3 language feature, and using the property access trick makes the implementation quite efficient, at least in modern browsers. In many cases, creating classes only when they are actually needed even increases performance, at least application start-up time, but also when many static dependencies are not actually needed at runtime at all.

Interfaces plus "is" and "as" operator

In ActionScript, interfaces are mainly used by the compiler for type checking. However, type checks can also be performed at runtime, using the AS3 operators instanceof, is, and as. instanceof only works for classes and automatically has the correct semantics when simulating classes via the prototype chain, so it can be used as-is in JavaScript.
x as T is simply defined as x is T ? x : null and would be implemented as a runtime helper function.
So the only operator we really have to simulate is is. To do so, we need some information about interfaces:

  • for a class, the set of interfaces it implements
  • for an interface, the set of interfaces it extends

For easy on-demand loading of the second information, we should implement interfaces as AMD modules, too. The AMD value of an interface could simply be set set of extended interfaces, but I chose to use a function that receives an object and sets the names of all extended interfaces as keys in that object (the values do not matter, they are simply true). This implementation makes it easy and efficient to unite interfaces: simply apply all interface functions on an empty object.
A class definition requires all AMD modules of interfaces it implements and can thus easily create the set of all interfaces it (transitively) implements. This set is stored as a static meta property, let's call it $implements.
It is convenient to have a check whether a given object implements an interface as a method isInstance(object) each interface provides. This method has to check whether the given object's constructor (its class) has a $implements property, and if so, whether the interface's name is contained in $implements:

Code Block

function isInstance(object) {
  return object !== null && typeof object === "object" &&
         !!object.constructor.$implements &&
         fullyQualifiedName in object.constructor.$implements;
}

where fullyQualifiedName is the fully qualified name of the interface.

The is function now has to tackle some special cases, then check instanceof, and finally check whether T is an interface, and if so, call its isInstance() method:

Code Block

function is(object, type) {
  return !!type && object !== undefined && object !== null &&
         (object instanceof type ||
          typeof type.isInstance === "function" && type.isInstance(object));
}

Packages

In ActionScript, packages do not exist at run-time, instead they lead to class and interface declarations having a fully qualified name to avoid name clashes.
To make a JavaScript expression referencing a fully qualified class resemble the ActionScript syntax, packages are often simulated by nested JavaScript objects, for example a class org.apache.flex.test.Foo would be assigned to a package object and then be used like so:

Code Block

if (!org) org = {};
if (!org.apache) org.apache = {};
if (!org.apache.flex) org.apache.flex = {};
if (!org.apache.flex.test) org.apache.flex.test = {};
org.apache.flex.test.Foo = Foo;

var foo = new org.apache.flex.test.Foo();

(Of course, the code to create a nested package would be moved to a helper function.)
Looking at how classes are usually used in ActionScript, it turns out that they have to be imported (in contrast to Java, even if they are used by their fully-qualified names in the code!) and are only used by their fully-qualified names in the rare case of local name clashes.
Also, nested object access to fully qualified identifiers is not very efficient.
In JavaScript, there is a better solution to accessing namespaced identifiers, namely modules. There are essentially two types of module definitions: synchronous (CommonJS-style) versus asynchronous module definitions / require-calls. While synchronous requires come from the server world (e.g. NodeJS), asynchronous requires better fit realities of module loading in the browser.
When a module with a fully qualified name / path is required (loaded), it can be assigned to a local variable or a callback parameter, so that there is no need to use the fully qualified name.
Only when "exporting" ActionScript classes or other identifiers for direct use from JavaScript, it may make sense to apply the pattern "packages as global nested objects".

So the current suggestion for packages is to define a class that is inside a package as a module with the fully qualified name of that class.

Consider this class

org/apache/flex/test/Foo.as:

Code Block

package org.apache.flex.test {
public class Foo {
  ...
}
}

and another class in another package using it:

org/apache/flex/test2/Baz.as:

Code Block

package org.apache.flex.test2 {
import org.apache.flex.test.Foo;
public class Baz {
  ... // use Foo...
}
}

In JavaScript AMD notation, these classes would look like so:

org/apache/flex/test/Foo.js:

Code Block

define("org/apache/flex/test/Foo", function() {
  function Foo() { ... }
  ...
  return Foo;
});

org/apache/flex/test2/Baz.js:

Code Block

define("org/apache/flex/test2/Baz", ["org/apache/flex/test/Foo"], function(Foo) {
  function Baz() { ... }
  ... // use Foo...
  return Baz;
});

...

packages do not exist at run-time, instead they lead to class and interface declarations having a fully qualified name to avoid name clashes.
To make a JavaScript expression referencing a fully qualified class resemble the ActionScript syntax, packages are often simulated by nested JavaScript objects, for example a class org.apache.flex.test.Foo would be assigned to a package object and then be used like so:

Code Block

if (!org) org = {};
if (!org.apache) org.apache = {};
if (!org.apache.flex) org.apache.flex = {};
if (!org.apache.flex.test) org.apache.flex.test = {};
org.apache.flex.test.Foo = Foo;

var foo = new org.apache.flex.test.Foo();

(Of course, the code to create a nested package would be moved to a helper function.)
Looking at how classes are usually used in ActionScript, it turns out that they have to be imported (in contrast to Java, even if they are used by their fully-qualified names in the code!) and are only used by their fully-qualified names in the rare case of local name clashes.
Also, nested object access to fully qualified identifiers is not very efficient.
In JavaScript, there is a better solution to accessing namespaced identifiers, namely modules. There are essentially two types of module definitions: synchronous (CommonJS-style) versus asynchronous module definitions / require-calls. While synchronous requires come from the server world (e.g. NodeJS), asynchronous requires better fit realities of module loading in the browser.
When a module with a fully qualified name / path is required (loaded), it can be assigned to a local variable or a callback parameter, so that there is no need to use the fully qualified name.
Only when "exporting" ActionScript classes or other identifiers for direct use from JavaScript, it may make sense to apply the pattern "packages as global nested objects".

So the current suggestion for packages is to define a class that is inside a package as a module with the fully qualified name of that class.

Consider this class

org/apache/flex/test/Foo.as:

Code Block

package org.apache.flex.test {
public class Foo {
  ...
}
}

and another class in another package using it:

org/apache/flex/test2/Baz.as:

Code Block

package org.apache.flex.test2 {
import org.apache.flex.test.Foo;
public class Baz {
  ... // use Foo...
}
}

In JavaScript AMD notation, these classes would look like so:

org/apache/flex/test/Foo.js:

Code Block

define("org/apache/flex/test/Foo", function() {
  function Foo() { ... }
  ...
  return Foo;
});

org/apache/flex/test2/Baz.js:

Code Block

define("org/apache/flex/test2/Baz", ["org/apache/flex/test/Foo"], function(Foo) {
  function Baz() { ... }
  ... // use Foo...
  return Baz;
});

The first argument to define can be left out when the definition is the only one contained in the file, then the module name is derived from the file name.

To implement another AS3 language feature, namely lazy class creation, the module return value will actually look different (see next section).

Static code execution / lazy class creation

In ActionScript, a class may not only contain static members, but also static code. Static fields may also have initializers which are quite similar to static code.
Static code and static initializers are executed exactly once when the class is created. A class is created right before code that references it is executed. Note that this has nothing to do with import directives, nor does a class trigger immediate initialization of all classes to which it has static dependencies.

No matter how class creation is triggered, it is always executed as a whole:

  1. first, its superclass is created (which creates its superclass transitively)
  2. then, all static initializers are evaluated, in the order in which they appear in the source code
  3. last, all static code statements are executed, in the order in which they appear in the source code

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 to resolve all static dependencies and load the code of all classes that are potentially needed at runtime, but create those classes only right before they are actually used.
This changes what our AMD modules return: Instead of the class / constructor function, the return value is a class definition that still has to be initialized to create the actual class.

A class definition is a factory object for a class. A trick for an efficient implementation of the factory method is to use a get property that, upon its first invocation, can be replaced by a simple field with a direct reference to the (now initialized) class. For the sake of brevity and readability (we want the reader to mainly ignore this helper property), let's call that property _ (underscore).

Using this approach, a reference to another class like

Code Block

import com.acme.OtherClass;

public class ThisClass {
  public function ThisClass() {
    OtherClass.doSomething();
  }
  ...
}

would in generated JavaScript become

Code Block

define(["com/acme/OtherClass"], function(OtherClass_) {
  return Object.defineProperty({}, "_", {
    configurable: true,
    get: function() {
      function ThisClass() {
        OtherClass_._.doSomething();
      }
      Object.defineProperty(this, "_", { value: ThisClass });
      ...
      return ThisClass;
    }
  });
});

Note how the definition of ThisClass, instead of directly returning its constructor function, returns an object with a get property _, and how its implementation replaces that property by a read-only reference to the constructor (that's why it has to be configurable).
Also note that we use OtherClass_ (postfixed by an underscore!) to be able to use the original name OtherClass for a local variable as a shortcut to the class when we know it has already been initialized, like so:

Code Block

define(["com/acme/OtherClass"], function(OtherClass_) {
  return Object.defineProperty({}, "_", {
    configurable: true,
    get: function() {
      var OtherClass;
      function ThisClass() {
        (OtherClass = OtherClass_._).doSomething();
        OtherClass.doSomethingElse();
        var x = OtherClass.CONSTANT;
        ...
      }
      Object.defineProperty(this, "_", { value: ThisClass });
      ...
      return ThisClass;
    }
  });
});

Since for the time being, we still target Internet Explorer 8 (or even 7), we need a solution that also works without get properties.
In order not to always need to perform a function call (which results in a runtime penalty and is distracting during debugging), we try to read the field first and only call the factory function in case the property is not yet defined. Following the general pattern of naming explicit get functions (see below), the factory function for IE8 would be called get$_, resulting in the following expression to access a potentially not-yet-created class: (OtherClass_._ || OtherClass_.get$_())

Granted, simulating lazy class creation makes the solution a bit more complex, but it is an important AS3 language feature, and using the property access trick makes the implementation quite efficient, at least in modern browsers. In many cases, creating classes only when they are actually needed even increases performance, at least application start-up time, but also when many static dependencies are not actually needed at runtime at all.

Interfaces plus "is" and "as" operator

In ActionScript, interfaces are mainly used by the compiler for type checking. However, type checks can also be performed at runtime, using the AS3 operators instanceof, is, and as. instanceof only works for classes and automatically has the correct semantics when simulating classes via the prototype chain, so it can be used as-is in JavaScript.
x as T is simply defined as x is T ? x : null and would be implemented as a runtime helper function.
So the only operator we really have to simulate is is. To do so, we need some information about interfaces:

  • for a class, the set of interfaces it implements
  • for an interface, the set of interfaces it extends

For easy on-demand loading of the second information, we should implement interfaces as AMD modules, too. The AMD value of an interface could simply be set set of extended interfaces, but I chose to use a function that receives an object and sets the names of all extended interfaces as keys in that object (the values do not matter, they are simply true). This implementation makes it easy and efficient to unite interfaces: simply apply all interface functions on an empty object.
A class definition requires all AMD modules of interfaces it implements and can thus easily create the set of all interfaces it (transitively) implements. This set is stored as a static meta property, let's call it $implements.
It is convenient to have a check whether a given object implements an interface as a method isInstance(object) each interface provides. This method has to check whether the given object's constructor (its class) has a $implements property, and if so, whether the interface's name is contained in $implements:

Code Block

function isInstance(object) {
  return object !== null && typeof object === "object" &&
         !!object.constructor.$implements &&
         fullyQualifiedName in object.constructor.$implements;
}

where fullyQualifiedName is the fully qualified name of the interface.

The is function now has to tackle some special cases, then check instanceof, and finally check whether T is an interface, and if so, call its isInstance() method:

Code Block

function is(object, type) {
  return !!type && object !== undefined && object !== null &&
         (object instanceof type ||
          typeof type.isInstance === "function" && type.isInstance(object));
}

Parameter default values

TODO

...