Classes, Inheritance, Interfaces, Packages, and Namespaces.

In this post about cross-compiling ActionScript to JavaScript I will talk about the following ActionScript language features and how those could be transformed to equivalent JavaScript code that exhibits identical runtime behavior as the ActionScript version:

  • classes and inheritance
  • methods and members
  • constructors
  • getter/setter
  • default parameters
  • interfaces
  • packages
  • namespaces

 

Classes and Inheritance

Class inheritance is one of the missing language features that we need to emulate.

// ActionScript:
public class Fruit
{
}

public class Orange extends Fruit
{
}

Fortunately almost every bigger JavaScript framework comes with utility functions that allow you to emulate classes and inheritance. To keep things simple I am going to use base.js from Google’s Closure Library framework. In this example Orange and Fruit are two example classes, which we can represent as empty functions. We let Orange derive from Fruit by using goog.inherits() from Google’s base.js.

// JavaScript:
var Fruit = function() {};
var Orange = function() {};
goog.inherits(Orange, Fruit);

 

 

Methods and Members

Classes may implement static methods and static members as well as instance methods and instance members. Those can easily be mapped to equivalent expressions in JavaScript:

// ActionScript:
public class MyClass
{
    public const instanceName : String = "MyClass Instance";
    public function getInstanceName() : String { return instanceName; }

    public static const staticName : String = "MyClass";
    public static function getClassName() : String { return staticName; }
}

The generated JavaScript code could look like this:

// JavaScript:
var MyClass = function() {};
MyClass.prototype.instanceName = "MyClass";
MyClass.prototype.getInstanceName = function() { return this.instanceName; };
MyClass.staticName = "MyClass";
MyClass.getClassName = function() { return MyClass.staticName; };

In JavaScript you always have to use “this” for accessing instance members (i.e. “this.instanceName”). You might have also noticed that odd transformation from “staticName” to “MyClass.staticName”. The cross-compiler needs to resolve names to either instance members (“this.instanceName”), static members (MyClass.staticName), or variable names.

 

Constructors

In ActionScript constructors are methods carrying the name of the class without any return type:

// ActionScript:
public class MyClass
{
    public function MyClass()
    {
        trace("MyClass ctor");
    }
}

In JavaScript for constructors we could simply reuse those functions that emulate classes:

// JavaScript:
var MyClass = function()
    {
        trace("MyClass ctor");
    };

There are more things to tell about constructors like calling super classes and injecting missing super() calls if a class extends another class that implements a constructor. I’ll get into those details another time.

 

Getter/Setter

ActionScript supports getter and setter functions.

// ActionScript:
 public class MyClass
 {
     private var m_name : String = "MyClass";
     public function get name() : String
     {
         return m_name;
     }
     public function set name(value:String) : void
     {
         m_name = value;
     }
 }
 var myClass : MyClass = new MyClass();
 myClass.name = "MyClass";
 trace( myClass.name );

It looks like JavaScript will eventually support getters and setters. Early versions of FalconJS did use ECMAScript5 getters and setters. It worked, but I also noticed a dramatic performance drop when using “native” JavaScript getters and setters. Browsers might have gotten better but if the performance is still a problem then you might have to take on getters/setters yourself. The generated JavaScript could look like this:

// JavaScript:
var MyClass = function() {};
MyClass.prototype.m_name = "MyClass";
MyClass.prototype.get_name = function() { return m_name; }
MyClass.prototype.set_name = function(value) { m_name = value; }
var myClass = new MyClass();
myClass.set_name("MyClass");
trace( myClass.get_name() );

Transforming myClass.name to myClass.get_name() and myClass.set_name() at compile time is not a trivial task and requires you to do a fair amount of static analysis. There are also cases where it is pretty much impossible to resolve an instance’s types. Fortunately most of those cases are caused by untyped code and could be easily fixed by introducing types in the source code.

 

Default Parameters

ActionScript supports default parameters, JavaScript doesn’t.

// ActionScript:
 public class MyClass
 {
     public function helloWorld( s1 : String = "Hello", 
                                 s2 : String = "World!" ) : String
     {
         return s1 + ", " + s2;
     }
 }

We could emulate default parameters in JavaScript as follows:

// JavaScript:
var MyClass = function() {};
MyClass.prototype.helloWorld = function(_default_s1, _default_s2)
{
     var s1 = (typeof(_default_s1) != "undefined") ? _default_s1 : "Hello";
     var s2 = (typeof(_default_s2) != "undefined") ? _default_s2 : "World!";
     return s1 + ", " + s2;
}

 

Interfaces.

Interfaces are not supported in JavaScript and present a problem if they appear on the right hand side of expressions with “is” and “instanceof”:

// ActionScript:
public interface IBall
{
    function roll() : void;
}
public class Ball implements IBall
{
    public function roll() : void
    {
    }
}
var ball : Ball = new Ball();
trace( ball is IBall ); // "true"

The big problem with transforming ActionScript’s interfaces to JavaScript is that one class can implement multiple interfaces in addition to extending classes, which also may implement multiple interfaces. This is a little bit of a nightmare and can probably only be solved by emitting additional class information that map class names to lists of interface names. Every interface can be represented by an Object while the “is” and “instanceof” operator need to be virtualized by injecting calls to some utility function, i.e. as3.instanceOf():

// JavaScript:
var IBall = {"_NAME": "IBall"};
var Ball = function() {};
Ball._NAME = "Ball";
Ball.prototype._CLASS = Ball; // the instance points to the class
Ball.prototype.roll = function() {};
as3.implements( Ball, [IBall]); // registers IBall for Ball.
trace( as3.instanceOf( ball, IBall ) ); // hopefully "true"

In order to be able to determine whether “ball” implements IBall and therefore is an instance of IBall we need to store the Ball class in the instance (i.e. _CLASS). Each class needs to carry a name (i.e. _NAME) and classes need to register their implemented interfaces via as3.implements(). With those additions in place we should be able to implement as3.instanceOf. Perhaps like this:

// JavaScript:
as3.instanceOf = function(instance, classOrInterface)
{
    if( instance instanceof classOrInterface )
        return true;
    var interfaces = as3.interfaceInfo[instance._CLASS._NAME];
    for( var interface in interfaces )
    {
        if( interface == classOrInterface._NAME )
            return true;
    }
    return false;
}

(Note that this version of as3.instanceOf does not traverse the super-chain.)

 

Packages

ActionScript encourages you to embed classes in packages:

// ActionScript:
package flash.display
{
    public class Sprite
    {
    }
}

There are no packages in JavaScript. But we could do this:

// JavaScript:
var flash = {};
flash.display = {};
flash.display.Sprite = function() {};

 

Namespaces

Namespaces are pretty complicated. ActionScript has a few built-in namespaces like “public”, “private”, “protected”, and “internal”.
But you can also create your own namespaces:

// ActionScript:
public namespace mx_internal = "http://www.adobe.com/2006/flex/mx/internal";

In JavaScript every property is public. There are tricks for hiding properties and functions, which you could use for emulating “private”.

But for now let’s just say that all namespaces should more or less be ignored and treated as “public”.