You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 4 Next »

Here, we collect all ActionScript 3 language features that are not present in ECMAScript 3 and document how we are going to simulate them.

General assumptions

We use

  • ECMAScript 5 strict mode whenever possible
  • ECMAScript 5 API (Object.create() etc.) that we know can be "polyfilled" in IE8 adequately
  • a well-known module / dependency mechanism like CommonJS modules or AMD
  • no other JavaScript library like jQuery, but "VanillaJS"

AS3 language features missing in ECMAScript 3

For every non-trivial language feature, we should introduce a dedicated child page.

Notation:

  • $0, $1, ... are auxiliary variables generated by the compiler, chosen to avoid name-clashes with any other identifiers in scope.
  • exp1, exp2, ... (italics) are arbitrary complex ActionScript expressions

Classes

Like most JavaScript frameworks, we simulate classes by a constructor function and its prototype chain.
The ES5 API Object.create(prototype,propertyDescriptors) serves this task very well.
While members are properties of the constructor function prototype, static members are properties of the constructor function itself.
Non-public members are discussed in a dedicated sub-section.

Class structure

The translation pattern is a follows:

public class Foo extends Bar {
  // constructor
  public function Foo(/*constructor parameters*/) {
    // constructor code
  }
  // member declarations
  ...
  // static member declarations
  ...
}

becomes

function Foo(/*constructor parameters*/) {
  // constructor code
}
Object.defineProperties(Foo, {
  ... // rewritten static member declarations
});
Foo.prototype = Object.create(Bar.prototype, {
  constructor: { value: Foo },
  ... // rewritten member declarations
});

The constructor property has to be defined explicitly, as otherwise, a subclass would inherit that property from its superclass, resulting in the subclass having the same constructor as its superclass.

Members and visibility (public, protected, internal, private)

Only public and protected members should be stored as properties of the constructor function / its prototype as-is. For protected members, access rights are checked by the compiler.
For private and internal members, we have to avoid name-clashes. There are different cases:

  • Private methods (non-static as well as static ones) and private static fields are declared in 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. 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.

Static code execution

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, let's call this "the class is initialized". A class is initialized 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 initialization is triggered, it is always executed as a whole:

  1. first, its superclass is initialized
  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

This AS3 feature is quite tricky to simulate efficiently without relying on ES5 get/set properties (see below).

Any reference to another class that could not yet be initialized would have to be wrapped by a function that triggers initialization if necessary:

OtherClass.something

would become

ensureInitialized(OtherClass).something

where ensureInitialized would be a runtime function that checks whether the given class is initialized, if not, perform the initialization, and in any case return the initialized class.
The problem is that this adds a function call to almost all expressions referring to another class, since it is often not possible to determine at compile time that another class must already have been initialized. Only superclasses are guaranteed to have been initialized before code in the current class may run.

Relying on ES5 get/set properties, self-initializing classes can be implemented much more efficiently. A potentially uninitialized class would be accessed as a property of a wrapping object, which could be the package object, e.g. acme.Foo. The trick is that property Foo is first defined by a get function that performs the initialization, then replaces itself by a simple read-only property with a direct reference to the (now initialized) class.

Another approach is to implement the most important cases more efficiently by wrapping the constructor and all static methods in initializing functions that first initialize the class, then execute the original code. This is the approach currently used by Frank's runtime prototype.

Interfaces plus "as" and "is" operator

TODO

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:

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:

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

and another class in another package using it:

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

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:

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

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

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.

Parameter default values

TODO

Rest (...) parameter

TODO

Bound methods

TODO

"this" is always in scope

TODO

Statements

TODO

for each

TODO

Operators

TODO

&&=

TODO

||=

TODO

  • No labels