From closures to prototypes, part 2
In part 1 of this post, I exposed the differences between the closure and the prototype patterns to define JavaScript classes. In this post, I'll show the shortest path to convert an existing class built using Atlas and closures to a prototype-based class. I'll also show a few caveats that you need to be aware of.
Here's an example of a class built using the July CTP of Atlas:
Custom.Color = function(r, g, b) { Custom.Color.initializeBase(this); var _r = r || 0; var _g = g || 0; var _b = b || 0; this.get_red = function() { return _r; } this.set_red = function(value) { _r = value; } this.get_green = function() { return _g; } this.set_green = function(value) { _g = value; } this.get_blue = function() { return _b; } this.set_blue = function(value) { _b = value; } this.toString = function() { return Custom.Color.callBaseMethod(this, "toString") + "(" + _r + "," + _g + "," + _b + ")"; } Custom.Color.registerBaseMethod(this, "toString"); } Custom.Color.registerClass("Custom.Color", Sys.Component);
The first thing you need to do is insert the following right after the private variable initialization, as this is all that's needed now in the constructor:
}
Custom.Color.prototype = {
Then you need to replace the variable initializations in the constructor with instance variable initialization:
this._r = r || 0; this._g = g || 0; this._b = b || 0;
The next step is of course to replace all occurrences of _r, _g and _b with this._r, this._g and this._b throughout the rest of the code.
Note the convention that private members (fields and methods) are prefixed with an underscore. This convention will be adopted by Visual Studio Orcas in IntelliSense.
For each method and property accessor, you need to remove the "this." before the name and replace the " = " with ": ". Also add a comma after the closing brace of each method but the last one. Finally, remove any registerBaseMethod you may have (in prototype-based classes, everything is virtual).
That's it, your class is now built using prototypes:
Custom.Color = function(r, g, b) { Custom.Color.initializeBase(this); this._r = r || 0; this._g = g || 0; this._b = b || 0; } Custom.Color.prototype = { get_red: function() { return this._r; }, set_red: function(value) { this._r = value; }, get_green: function() { return this._g; }, set_green: function(value) { this._g = value; }, get_blue: function() { return this._b; }, set_blue: function(value) { this._b = value; }, toString: function() { return Custom.Color.callBaseMethod(this, "toString") + "(" + this._r + "," + this._g + "," + this._b + ")"; } } Custom.Color.registerClass("Custom.Color", Sys.component);
I've highlighted where the code differs in both samples for easy reference.
So that was easy. Now let's enumerate a few do's and don't's...
- Do transform private constructor variables into underscore-prefixed instance fields.
Foo.Bar = function() { var _baz = ""; }
becomesFoo.Bar = function() { this._baz: "" }
- Do move all methods and property accessors from the constructor to the prototype.
- Do update all references to fields and methods to be dereferenced through the this pointer.
_member
becomesthis._member
- Do separate prototype members with commas, but don't leave a comma after the last one (JSON syntax doesn't allow it).
- Don't use the instanceof operator with Atlas classes. Do use
MyNamespace.MyType.isInstanceOfType(myInstance)
instead ofmyInstance instanceof MyNamespace.MyType
- Do continue to set initial values of fields from the constructor.
- Don't move field initializations that depend on the this pointer to prototype (but do make them fields)
Foo.Bar = function() { var _delegate = Function.createDelegate(this, this._handler); }
becomesFoo.Bar = function() { this._delegate = Function.createDelegate(this, this._handler); }
- Don't register base methods: with the prototype pattern, all methods are virtual. The registerBaseMethod will not be available in Atlas starting with Beta 1, which makes it impossible for closure-based classes to be base Atlas classes.
- Do move private functions to the prototype (prefixing the name with underscore) unless they have a compelling use of the closure pattern (for security for example), which is quite rare.
Foo.Bar = function() { function privateMethod() { //... } }
becomes
Foo.Bar = function() {} Foo.Bar.prototype = { _privateMethod: function() { //... } }
In the next posts, I'll get into other specific changes that we're making in Beta 1.
Read part 1 here: http://weblogs.asp.net/bleroy/archive/2006/10/11/F...