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.
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, too, resulting in the subclass having the same constructor as its superclass.
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
Class member visibility (public, protected, internal, private)
TODO
Statements
TODO
for each
TODO
Operators
TODO
&&=
TODO
||=
TODO