Classical Object-Oriented
JavaScript Framework
Download 0.2.9
Released: 07 Nov 2017
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.
const { Class } = easejs;

const Stack = Class( 'Stack',
'private _stack': [],

'public push'( value )
this._stack.push( value );
},

'public pop'()
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
const Foo = Class(
'private _name': '',

constructor( name )
this._name = ''+( name );
},

'public sayHello'()
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
const inst_a = Foo( "John Doe" );
const inst_b = 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
).
const Cow = Class( 'Cow',
'virtual public tip'()
return "Omph.";
},
} );

const SturdyCow = Class( 'SturdyCow' )
.extend( Cow,
'override public tip'()
return "Moo.";
},
} );
Alternatively, if creating an anonymous subtype, the supertype's
extend()
method may be used.
const SturdyCow = Cow.extend( { /*...*/ } );
Type checks for polymorphic methods may be performed with
Class.isA()
, which is
recommended in
place of
instanceof
const cow = Cow();
const 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.
const Foo = FinalClass( 'Foo',
'public describe'()
return "I cannot be extended.";
},
} );
→ Read more in manual
Traits As Mixins
Trait support was introduced in celebration of becoming a GNU
project. It is currently under development and has not yet been
finalized, but has been included in each GNU ease.js release since v0.2.0,
and is stable.
const Echo = Class( 'Echo',
'virtual public echo'( str )
return str;
},
} );

const Prefix = Trait( 'Prefix' )
.extend( Echo,
'private _prefix': '',

__mixin( prefix )
this._prefix = ''+prefix;
},

'public abstract override echo'( str )
return this._prefix + this.__super( str );
},
} );

const Suffix = Trait( 'Suffix' )
.extend( Echo,
'private _suffix': '',

__mixin( suffix )
this._suffix = ''+suffix;
},

'public abstract override echo'( str )
return this.__super( str ) + this._suffix;
},
} );

const UpperCase = Trait( 'UpperCase' )
.extend( Echo,
'public abstract override echo'( str )
return this.__super( str ).toUpperCase();
} );

// stackable, parameterized traits
Echo.use( Prefix( "Bar" ) )
.use( Suffix( "Baz" ) )
.use( UpperCase )
.use( Prefix( "Foo" ) )
.use( Suffix( "Quux" ) )().echo( "Inner" );

// result: FooBARINNERBAZQuux
Documentation will be available once some final details are
finalized. Until that time,
the
test
cases provide extensive examples and rationale
. The following posts
also summarize some of the features:
Access Modifiers
All three common access modifiers—public, protected and
private—are supported, but
enforced only in ECMAScript 5 and later
environments.
const DatabaseRecord = Class( 'DatabaseRecord',
'private _connection': null,

constructor( host, user, pass )
this._connection = this._connect( host, user, pass );
},

'private _connect'( host, user, pass )
// (do connection stuff)
return { host: host };
},

'protected query'( query )
// perform query on this._connection, rather than exposing
// this._connection to subtypes
},

'protected escapeString'( field )
return field.replace( "'", "\\'" );
},

'public getName'( 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:
const DatabaseRecord = Class( 'DatabaseRecord',
/* implicitly private */
_connection: null,

constructor( host, user, pass )
this._connection = this._connect( host, user, pass );
},

/* implicitly private */
_connect( host, user, pass )
// (do connection stuff)
return { host: host };
},

'protected query'( query )
// perform query on this._connection, rather than exposing
// this._connection to subtypes
},

'protected escapeString'( field )
return field.replace( "'", "\\'" );
},

/* public by default */
getName( id )
return this._query(
"SELECT name FROM users WHERE id = '" +
this._escapeString( id ) + "' LIMIT 1"
);
},
} );
→ 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.
const Database = AbstractClass( 'Database',
'public connect'( 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' ],
} );

const MongoDatabase = Class( 'MongoDatabase' )
.extend( Database,
// must implement each argument for Database.authenticate()
'protected authenticate'( 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.
const 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
const ConcreteFilesystem = Class( 'ConcreteFilesystem' )
.implement( Filesystem ) // multiple interfaces as separate arguments
'public open'( path, mode )
return { path: path, mode: mode };
},

'public read'( handle, length )
return "";
},

'public write'( handle, data )
// ...
return data.length;
},

'public close'( handle )
// ...
return this;
},
} );
Polymorphic methods may check whether a given object implements a certain
interface.
const inst = ConcreteFilesystem();
Class.isA( Filesystem, inst ); // true
→ 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—
$()
const Cow = Class( 'Cow',
'const LEGS': 4,

'private static _number': 0,

constructor()
// __self refers to the class associated with this instance
this.__self.$( '_number' ) = this.__self.$( '_number' ) + 1;
},

'public static create'()
return Cow();
},

'public static getNumber'()
return this.__self.$( '_number' );
},
} );

Cow.$( 'LEGS' ); // 4
Cow.getNumber(); // 0
Cow.create();
Cow.getNumber(); // 1
→ Read more in manual
Transparent Error Subtyping
Error subtyping (creating your own error types) in ECMAScript is
notoriously crude, and getting it to work intuitively is even
harder. ease.js transparently handles all necessarily boilerplate when
extending
Error
or its subtypes.
const MyError = Class( 'MyError' )
.extend( Error, {} );

const e = MyError( 'Foo' );
e.message; // Foo
e.name; // MyError

// -- if supported by environment --
e.stack; // stack beginning at caller
e.fileName; // caller filename
e.lineNumber; // caller line number
e.columnNumber; // caller column number

// general case
throw MyError( 'Foo' );
→ Read more in manual