Proxy

My previous post about Weak References got longer and longer. That’s why I decided to split up my post about Weak References, Proxies, and Dictionary, into smaller, more manageable pieces.

 

Intercepting access to properties

According to the ActionScript 3.0 Language Reference flash.utils.Proxy lets you override the default behavior of ActionScript operations (such as retrieving and modifying properties) on an object. Those operations are:

  • getting the value of a property
  • setting the value of a property
  • calling a method
  • deleting a property
  • iterating for-in loops
  • iterating for-each loops
  • E4X operations

The idea is very simple but powerful. If you let a class derive from Proxy you can intercept any of those operations listed above and plug in your own behavior. This feature is very useful if you need to implement something like remote objects, where an instance of a class acts like a facade for a different object that may live on a different machine. An implementation of remote objects based on Proxy would then forward those operations to the “real” object.

Being able to intercept operations at such a low level obviously requires support from the virtual machine, or as in our case the browser. Apparently, the concept of Proxy seems so useful that it might get baked into future versions of JavaScript. If you are interested in more details of ECMAScript 6’s Proxy proposal I recommend reading Nicholas Zakas’s article about Experimenting with ECMAScript 6 proxies.

 

Stubborn Five

In order to keep it simple I am going to use an example of a proxy class that only intercepts getting and setting property values.

// ActionScript:
dynamic class Five extends Proxy
{
    override flash_proxy function getProperty(name:*):* { return 5; }
    override flash_proxy function setProperty(name:*, value:*):void { }
 }
var five : Five = new Five();
five.one = 1;
trace(five.one);

This is perhaps one of the most useless classes I have ever (intentionally) implemented. My Five class only supports 5 as a value for properties. Since Five is declared as dynamic the compiler treats it as if it were an Object and allows accessing any properties (i.e. one) without throwing syntax errors. In our example we assign 1 to the one property, which is being intercepted and redirected to  setProperty , which does nothing. If somebody asks for the value of  the one property (or any other property for that matter) we just return 5 through the intercepted getter, which is implemented by getProperty.

 

Cross-compiling Proxy classes

When cross-compiling our Five class to JavaScript we could just ignore the flash_proxy namespace and generate JavaScript as we would for any other class.

// JavaScript:
var Five = function() {};
Five.prototype.getProperty(name) { return 5; }
Five.prototype.setProperty(name, value) { }

But the code that actually uses an instance of a class derived from Proxy needs to be modified:

// JavaScript
var five = new Five();
five.setProperty("one", 5);
trace( five.getProperty("one") );

Of course, this all is easier said than done.

 

Advanced but broken

Accessing properties via strings is not recommended if you are planning on optimizing your generated JavaScript code with Google’s Closure compiler and with its powerful ADVANCED_OPTIMIZATIONS mode. The only way I know of in order to make proxies work with ADVANCED_OPTIMIZATIONS is injecting additional code that exports symbols:

// JavaScript
var five = new Five();
five.setProperty("one", 5);
goog.export("one", five.one, five);
trace( five.getProperty("one") );

FalconJS does support proxies but is currently not smart enough to inject export instructions as shown above. For now you might be better off using SIMPLE_OPTIMIZATIONS when cross-compiling ActionScript code that uses proxies.

 

Dealing with untyped code

As you can imagine proxy code injections (all injections, really) become increasingly difficult if the code lacks type annotations. Untyped code is a nightmare for tools like FalconJS that attempt to statically analyze code.

Consider this example:

// ActionScipt:
var five : Object = new Five();
five.one = 1;
trace(five.one);

Without knowing that the variable five is of type Five your cross-compiler will have a hard time figuring out whether getProperty and setProperty need to be injected.

In order to handle untyped variables FalconJS emits code that is slightly different than the transformation I first suggested.

// JavaScript:
var five = new Five();
as3.setProperty(this, five, "one", 5);
trace( as3.getProperty(this, five, "one") );

This transformation will work if as3.getProperty and as3.setProperty were smart enough to figure out that five is of type Five and that Five is derived from Proxy. Here is how as3.getProperty could look like:

// ActionScript:
public class as3
{
    ...
    public static function getProperty(caller:Object,
                           instance:Object, property:String) : *
    {
        if( as3.isProxy(instance) )
            return instance.getProperty(property); 
        return instance[property];
    }
}

If you are interested in how you could implement as3.isProxy please read the chapter about Interfaces in my post about Classes, Inheritance, Interfaces, Packages, and Namespaces. Why does as3.getProperty have an additional caller parameter? I’ll explain that another time!