Classical Object-Oriented
JavaScript Framework
GNU ease.js is a Classical Object-Oriented framework for JavaScript, intended to eliminate boilerplate code and “ease” the transition into JavaScript from other Object-Oriented languages. Features include:
GNU ease.js is a framework, not a compiler. It may be used wherever JavaScript may be used, and supports all major browsers; ease.js also provides support for older, pre-ES5 environments by gracefully degrading features (such as visibility support) while remaining functionally consistent.
This project is part of the GNU Project.
Simple and Intuitive Class Definitions ¶
Class definitions closely resemble the familiar syntax of languages like Java and PHP.
var Class = easejs.Class; var Stack = Class( 'Stack', { 'private _stack': [], 'public push': function( value ) { this._stack.push( value ); }, 'public pop': function() { return this._stack.pop(); }, } );
Classes can be anonymous or named, the latter being more useful for debugging. Since classes may be anonymous, constructors are styled after PHP.
var Foo = Class( 'private _name': '', __construct: function( name ) { this._name = ''+( name ); }, 'public sayHello': function() { return this._name + " says 'Hello!'"; }, );
Classes can be instantiated with or without the new keyword. Omission aids in concise method chaining and the use of temporary instances.
var inst = Foo( "John Doe" ); var inst = new Foo( "John Doe" ); // temporary instance Foo( "John Doe" ).sayHello();→ Read more in manual ↑
Classical Inheritance ¶
Classes can be extended to create subtypes. Like C++, methods are not virtual by default. In Java terminology, all methods are final by default. Multiple inheritance, like Java, is unsupported (see Interfaces).
var Cow = Class( 'Cow', { 'virtual public tip': function() { return "Omph."; }, } ); var SturdyCow = Class( 'SturdyCow' ) .extend( Cow, { 'override public tip': function() { return "Moo."; }, } );
Alternatively, if creating an anonymous subtype, the supertype's extend() method may be used.
var SturdyCow = Cow.extend( { /*...*/ } );
Type checks for polymorphic methods may be performed with Class.isA(), which is recommended in place of instanceof.
var cow = Cow(), sturdy = SturdyCow(); Class.isA( Cow, cow ); // true Class.isA( SturdyCow, cow ); // false Class.isA( Cow, sturdy ); // true Class.isA( SturdyCow, sturdy ); // true
To prevent a class from being extended, FinalClass may be used.
var Foo = FinalClass( 'Foo', { 'public describe': function() { return "I cannot be extended."; }, } );→ Read more in manual ↑
Abstract Classes and Methods ¶
If a class contains abstract members, it must be declared as an AbstractClass. Abstract methods must be overridden by subtypes and are implicitly virtual.
var Database = AbstractClass( 'Database', { 'public connect': function( user, pass ) { if ( !( this.authenticate( user, pass ) ) ) { throw Error( "Authentication failed." ); } }, // abstract methods define arguments as an array of strings 'abstract protected authenticate': [ 'user', 'pass' ], } ); var MongoDatabase = Class( 'MongoDatabase' ) .extend( Database, { // must implement each argument for Database.authenticate() 'protected authenticate': function( user, pass ) { // ... }, } );→ Read more in manual ↑
Interfaces ¶
ease.js supports the Java concept of Interfaces, which act much like abstract classes with no implementation. Each method is implicitly abstract. Properties cannot be defined on interfaces.
var Filesystem = Interface( 'Filesystem', { 'public open': [ 'path', 'mode' ], 'public read': [ 'handle', 'length' ], 'public write': [ 'handle', 'data' ], 'public close': [ 'handle' ], } );
Concrete classes may implement one or more interfaces. If a concrete class does not provide a concrete implementation for every method defined on the interface, it must be declared an AbstractClass.
var ConcreteFilesystem = Class( 'ConcreteFilesystem' ) .implement( Filesystem ) // multiple interfaces as separate arguments { 'public open': function( path, mode ) { return { path: path, mode: mode }; }, 'public read': function( handle, length ) { return ""; }, 'public write': function( handle, data ) { // ... return data.length; }, 'public close': function( handle ) { // ... return this; }, } );
Polymorphic methods may check whether a given object implements a certain interface.
var inst = ConcreteFilesystem(); Class.isA( Filesystem, inst ); // true→ Read more in manual ↑
Access Modifiers ¶
All three common access modifiers—public, protected and private—are supported, but enforced only in ECMAScript 5 and later environments.
var DatabaseRecord = Class( 'DatabaseRecord', { 'private _connection': null, __construct: function( host, user, pass ) { this._connection = this._connect( host, user, pass ); }, 'private _connect': function( host, user, pass ) { // (do connection stuff) return { host: host }; }, 'protected query': function( query ) { // perform query on this._connection, rather than exposing // this._connection to subtypes }, 'protected escapeString': function( field ) { return field.replace( "'", "\\'" ); }, 'public getName': function( id ) { return this._query( "SELECT name FROM users WHERE id = '" + this._escapeString( id ) + "' LIMIT 1" ); }, } );
In the above example, the database connection remains encapsulated within DatabaseRecord. Subtypes are able to query and escape strings and external callers are able to retrieve a name for a given id. Attempting to access a private or protected member externally will result in an error. Attempting to access a private member from within a subtype will result in an error.
Alternatively, a more concise style may be used, which is more natural to users of JavaScript's native prototype model:
→ Read more in manual ↑
Static and Constant Members ¶
Static members are bound to the class itself, rather than a particular instance. Constants are immutable static members (unlike languages like PHP, they may use any access modifier). In order to support both pre- and post-ECMAScript 5 environments, the syntax requires use of a static accessor method—$().
var Cow = Class( 'Cow', { 'const LEGS': 4, 'private static _number': 0, __construct: function() { // __self refers to the class associated with this instance this.__self.$( '_number' ) = this.__self.$( 'number' ) + 1; }, 'public static create': function() { return Cow(); }, 'public static getNumber': function(){ { return this.__self.$( '_number' ); }, } ); Cow.$( 'LEGS' ); // 4 Cow.getNumber(); // 0 Cow.create(); Cow.getNumber(); // 1→ Read more in manual ↑