Upgrading from JS.Class 1.x to 2.x
If you followed the documentation, the vast majority of your code will be compatible with version 2.x. Some undocumented methods and objects have been removed, but if you’ve been sticking to the publicized API you should be fine. This article lists known differences between version 1.x and version 2.x.
The new
keyword is not optional
In 1.x, you could create classes and modules without using the new
keyword:
var MyClass = JS.Class({ initialize: function() { /* ... */ } });
The documentation examples all used new
so if you followed along your code
will continue to work. In 2.x you must use new
to create modules, classes,
interfaces, singletons, decorators, and proxies.
var MyClass = new JS.Class({ initialize: function() { /* ... */ } }); var MyMod = new JS.Module({ doSomething: function() { /* ... */ } }); var MyDeco = new JS.Decorator(MyClass, { extraFunc: function() { /* ... */ } });
This is a consequence of the fact that JS.Class
and JS.Module
are
themselves classes in 2.x. In 1.x they were simple functions so the new
keyword has no real effect.
Modules can have singleton properties
In 1.x, JS.Module
was simply a shortcut for putting a set of methods inside
a closure so they could not be access unless you mixed the module into a class.
It was implemented as follows:
JS.Module = function(source) { return { included: function(klass) { klass.include(source); }, extended: function(klass) { klass.extend(source); } }; };
It just took a set of methods and wrapped them inside hooks for use with
include()
and extend()
. In version 2.x, JS.Module
is a fully-fledged
class in its own right and thus behaves differently. The main difference is
the behaviour of the extend
keyword—in 1.x, extend
adds class methods
to a class that include
-s the module, and in 2.x extend
adds singleton
methods to the module itself, not to the class.
Consider this example:
var Runnable = new JS.Module({ extend: { runAll: function() { return 'Run all'; } }, run: function() { return 'Run an instance'; } }); var Process = new JS.Class({ include: Runnable });
In version 1.x, extend
adds methods to classes that include
the module:
// Works in 1.x Process.runAll() // -> "Run all"
In version 2.x, extend
adds methods to the module itself, but not to classes
that include
it.
// Works in 2.x Runnable.runAll() // -> "Run all"
In all versions, the run()
method from Runnable
is added as an instance
method to Process
. It is not a method on Runnable
, in that you cannot call
Runnable.run()
, it is a method stored inside the module Runnable
.
// Works everywhere (new Process).run() // -> "Run an instance"
If you want the 1.x behaviour, you need two modules: one to store instance
methods and one to store class methods. Use an included()
hook so the
including class does not have to make multiple include
calls.
var Runnable = new JS.Module({ extend: { ClassMethods: new JS.Module({ runAll: function() { return 'Run all'; } }), included: function(base) { base.extend(this.ClassMethods); } }, run: function() { return 'Run an instance'; } }); var Process = new JS.Class({ include: Runnable });
The run()
method is added as an instance method from the Runnable
module.
The included()
hook extends Process
(the base
) using the module
Runnable.ClassMethods
, so Process
gets a runAll()
class method.
Process.runAll() // -> "Run all" (new Process).run() // -> "Run an instance"
This technique is consistent with the Ruby way of getting the same behaviour.
callSuper()
can call module methods
2.x fully implements Ruby’s inheritance system, whereas 1.x was incomplete. In
particular, 1.x did not allow callSuper()
to call methods from mixed-in
modules. Consider this example:
var Employee = new JS.Class({ hire: function() { return "Hiring an Employee"; } }); var Employable = new JS.Module({ hire: function() { return "I'm Employable"; } }); var BusDriver = new JS.Class(Employee, { include: Employable, hire: function() { return this.callSuper(); } }); (new BusDriver).hire()
In version 1.x, the final line evaluates to "Hiring an Employee"
(from
Employee
, the parent class), but in 2.x it returns "I'm Employable"
. This
is because mixed in Module
objects become part of the inheritance tree
according to Ruby’s inheritance semantics.