Modifying Core Types in ActionScript 3 Using the Prototype Object

ActionScript 3 has a Javascript lineage. It was essentially a fork of Ecmascript. Plus, the Adobe team has worked hard on making the language a proper superset of the Ecmascript specification. This is why although ActionScript 3 was a major architechtural change from 2, and as a result became much more like Java than Javascript, it still has support for the prototype object

When I learned about the prototype object, it was a fresh air from traditional class-based OOP. Studying cool libraries like prototype.js made me realize that the prototype model makes javascript far more flexible than some other strictly class-based OO languages.

One of the cool things you can do with the prototype object in javascript is modify the core classes of the language, like Array, String, and Date. You can do this in ActionScript 3 too, for it conforms to Ecmascript 4. For example, let's say you want to write the collect function for arrays a la ruby. You would do:
Array.prototype.collect = function(f){
  var ret = [];
  for each(var it in this) ret.push(f(it));
  return ret;
}
There you see me using the nice  for each syntax. But, this is going to break the behavior of the array, because now the collect function is going to show up in the enumeration in the for each loops. We don't want that, so to fix that we are going to set the collect attribute of Array.prototype to not be enumerable:
Array.prototype.setPropertyIsEnumerable('collect', false);
This allows you to write the following code:
[1,2,3].collect(function(i){ return i * 2; });
// result would be [2,4,6]
It's kinda tedious to have to setPropertyIsEnumerable for every time we add a function to an existing type, so I wrote a convience function:
function addMethodsTo(cls:Class, methods){
    for (var name:String in methods){
        cls.prototype[name] = methods[name];
        cls.prototype.setPropertyIsEnumerable(name, false);
    }
}
Which you can use like so:
addMethodsTo(Array, {
    collect: function(f){
        var ret = [];
        for each(var it in this) ret.push(f(it));
        return ret;
    },
    anotherMethod: function(){
        ...
    },
    ...
});
This is sort of like the style prototype.js uses for extending/creating classes.

A Word of Caution
Before you consider going further with this, I must advice you to think twice before using this technique(however, I hope you do decide to use it afterwards ;). There are several caveats:
  1. The prototype-based style is a second-class citizen in the world of ActionScript 3 and Flex. The Adobe team as well as the community seem to be much more committed to the class-based approach. I will describe some of the rough edges below.
  2. You will give up compile time type checking for the portions of your code that use this style.
  3. Prototype inheritence is handled by a completely different mechanism than class-based inheritence in the Flash VM and is not as performant.

Where to put this Code?
You saw the code example above, but, where do you put it? Since I decided to use a helper function(addMethodsTo), the code cannot be directly pasted inside the class scope of a class unless the addMethodsTo function is declared to be static(you can only make a method call directly inside class scope if it is a class method). As, a general solution, I'd rather the code be portable. So it should be includable in both class and function scope, and also, I'd like it not to pollute any namespaces.

My current solution is to put this bit of code inside an anonymous function which immediately gets executed:
// contents of includes/Array.as
(function(){
    include 'addMethodsTo.as';
    addMethodsTo(Array, {
        collect: function(f){
            var ret = [];
            for each(var it in this) ret.push(f(it));
            return ret;
        }
    });
})();
The addMethodTo function is pulled into a separate file to be easily includable else where.
So, with this, I would do the following to include this Array functionality:
include 'includes/Array.as';
Head over to Github for the complete structure of the files.

These methods are added during runtime - at exactly the time the above line of included code is executed, and not a moment before. I like to do the include at the top level of the application, this way the entire program has immediate access to the new methods. Oh, and when I said entire program, I do mean the entire program - not just the files that happen to include the file. Well, this is good and bad. This means if you create components that use the array extensions you've created without explicitly including it(you've included it at the entry point of your program), then you have created an invisible dependency. If you try to take the component and use it in a different project without the array extensions, it will not work. Of course, you could also make the dependency explicit by including the file everywhere you are using them, but 1) that's kinda tedious/repetitious, and 2) there's nothing enforcing you to do this.

Pitfalls and Gotchas
As I mentioned, the prototype-based programming style is a second-class citizen in the ActionScript 3 world, and so, its use is not particularly well supported. First of all, the compiler does not recognize any of the methods added via the prototype mechanism, and thus cannot perform static type checking on them. But what is funny is the way the compiler copes with this - it depends...on the type in question. For Arrays, the compiler simply allows all method calls - you can call any method on an array, even if it doesn't exist, the compiler won't complain. So, for a call like:
[1, 2, 3].collect(...);
The compiler won't even say a word. But for Dates, it gives a warning message. This:
new Date().format()
would trigger this warning:
Warning: format is not a recognized method of the dynamic class Date.
But, for strings, it's a different story still:
'one, two, three'.csv2Array()
compiler says:
Error: Call to a possibly undefined method csv2Array through a 
    reference with static type String.
And the same thing with numbers:
(2).minutes().ago()
compiler says:
Error: Call to a possibly undefined method minutes through a 
    reference with static type int.
The work around for strings and numbers is to upcast it to an Object:
Object('one, two, three').csv2Array();
Object(2).minutes().ago();
or if you just have an untyped variable, it'll work just fine:
var x = 2;
x.minutes().ago();
See the full code example here, and the demo here.

What about runtime error handling? So let's see what happens if you don't include the extensions and run the code. When I took out the array extension methods, the runtime dutifully throw me the:
TypeError: Error #1006: collect is not a function.
This is good, just what I would expect. For Date, it works the same way. Now let's try taking out the string extensions:
TypeError: Error #1006: value is not a function.
Uhh, what? Not really sure what you mean. And as you would expect, it works like this for Number as well.

I have also seen cases where the runtime simply completely muffles an error when there's an undefined method being tried as someone documented here. But I am not able to reproduce this just now. Also on another note, the error stacktrace from the flash player(debug version) is not very helpful because although it gives the call stack, there are no line numbers. I am sure that you'd get a better experience using Flex Builder, however.

I think that about wraps it up. Although extending core types is fun, powerful, and elegent, it's also full of holes and flying scissors everywhere. Are you ready to jump into this brave new world?

A message to the Adobe Flex/ActionScript team: I implore you to put more effort into the prototype-based side of the language. It allows for many possibilities which its class-based counterpart cannot offer. I don't dislike the class-based approach. I think the two each have their own strengths and weakness. Which is why I love this hybrid aspect of ActionScript 3 which allows me to use either style in the same environment. I believe that if the prototype-based aspect of ActionScript were to improve and get more exposure, it would not only become a better language, but also a more wide spread language.
Matt said 6 months ago
Very interesting. What about overriding methods in addition to adding new ones? So you can add Array.newMethod, but what if you want to override the pop method? Array.prototype.pop = function() { trace("My new pop method."); }; doesn't work because AS3 looks in the class first before looking in the prototype and finds the pop method there. (Although "pop" specificially may be in the prototype, I don't remember, but BitmapData.draw for instance; something that is definitely defined in the class.)
Krilnon said 6 months ago
AFAIK, there is no way to prototype-override methods like BitmapData#draw because they are defined in the public namespace, unlike the top-level class methods, which are defined in the AS3 namespace and defined again functions on the class's prototype object.  Methods (and properties in general) that are in a class's trait object will always take priority over prototype methods as long as the trait-method's namespace is open in the current scope.
Toby said 6 months ago
Matt, you cannot override Array's pop method because by default, the core types are implemented using traits. However, there's a compiler option to change this to prototype-based instead. See this article (at the bottom) for more details on this. I believe once you change that compiler flag(I haven't tried it personally), that you will be able to override Array's pop method. But, for other classes that use class-based inheritance you are still out of luck.
Tim Oxley said 4 months ago
I think you should qualify your message to adobe with some more objective, compelling reasons instead of "I think..." "I like..." etc.
xethorn said 4 months ago
Great article. I am currently doing some prototyping things. Your article is a must to read before starting. I found another issue, let's say: I want to add count() to Array. I do:

Array.prototype.count = function() { return this.lenght };

And, two lines under:

class NewTypeArray extends Array {
    public function NewTypeArray() {
       this.count(); // won't work.
       var ar:Array = new Array();
       ar.count(); // will work.
    }
}

It seems the NewTypeArray can't access to the count method.
Toby said 3 months ago
xethorn: try making it a dynamic class(I believe it will work):

dynamic class NewTypeArray extends Array {...}
xethorn said 2 months ago
Works perfectly, thanks!
oshyshko said about 1 month ago
Is there a workaround for adding static methods?
addStaticMethodsTo(Array, {
    range: function(from:int, to:int):Array { ... }
})

var a:Array = Array.range(1, 5) // [1, 2, 3, 4, 5]
// ^^ compiler says: Call to a possibly undefined method range through a reference with static type Class.

function addStaticMethodsTo(c:Class, methods:Object):void {
    for (var m:String in methods) { c[m] = methods[m] }
}
David R said 8 days ago
Prototype-based stuff was actually a bit more powerful in Actionscript 2. In fact, in Actionscript 1, prototype was the ONLY way to go...there was no class Foo { function bar() {} } syntax at all, only prototype based!

But with AS3, I think they decided class-based is the way to go, as it is stricter and can be better optimized.
Og2t said 1 day ago
Great article, I thought prototype was completely dead in AS3 – thanks for bringing it back!
I am planning to use it and add some useful props (like autoAlpha) to display objects.

How about proxies as the alternative way to prototype extending?
I stuck a wee demo class here: http://gist.github.com/327799
The downside is that you have to extend global classes and give them new names.

Best,
Tomek

Leave Comment

optional