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:
- 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.
- You will give up compile time type checking for the portions of your code that use this style.
- 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?
// 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;
}
});
})();
include 'includes/Array.as';
Pitfalls and Gotchas
[1, 2, 3].collect(...);
new Date().format()
Warning: format is not a recognized method of the dynamic class Date.
'one, two, three'.csv2Array()
Error: Call to a possibly undefined method csv2Array through a
reference with static type String.
(2).minutes().ago()
Error: Call to a possibly undefined method minutes through a
reference with static type int.
Object('one, two, three').csv2Array();
Object(2).minutes().ago();
var x = 2;
x.minutes().ago();
TypeError: Error #1006: collect is not a function.
TypeError: Error #1006: value is not a function.