jClassy

Class system in JavaScript

A JavaScript library is proposed and implemented, providing a class-system core for building object-oriented JavaScript programs.
Author
Ondřej Pavlata
Jablonec nad Nisou
Czech Republic
Document date
Initial releaseNovember 4, 2011
Last major release November 4, 2011
Last updateDecember 7, 2011
Download
  1. jclassy-0.4.2.js (uncompressed)
  2. jclassy-0.4.2.min.js (minified)
Warning
  1. This document has been created without any prepublication review except those made by the author himself.

Table of contents

Introduction

Main features
Suitability conditions
The jClassy library will presumably suit your needs proportionally to the following conditions:

 

Axial nodes

Axial nodes (classes, instances and prototypes)
Axial nodes are JavaScript objects that are (most) prominent to the class-system. There are four types of axial nodes according to the following table.
Axial Type of xTerminology
'$instance' x is an instance
'$iproto' x is an instance prototype
'$cproto' x is a class prototype
'$class' x is a class
In particular, a class is an axial node of type '$class'.

Indentation indicates that instance/class prototypes should be considered internal objects of the class system, usually not directly referenced by an application. In most cases, applications refer just to classes and instances.

The following code shows examples of axial nodes.

The .$class map
Axial nodes are mapped to classes via the .$class reference. The following rules hold: If A is a class and a an instance such that a.$class == A then a is said to be a direct instance of A and A is said to be the class of a.
Paths to axial nodes
Classes have pathnames according to nesting introduced later. For diagnostics / documentation purposes, we adopt the convention that for a class x with a pathname p, In addition, if a is a direct instance of x, then the opaque pathname of a is p + '##'.

Inheritance

JavaScript prototypal inheritance
JavaScript object property lookup is based on prototypal inheritance. It is a forest on JavaScript objects with the parent partial function being an internal property denoted by [[Prototype]], and in some implementations also made available as the .__proto__ reference. In ECMAScript, 5th Edition, o.[[Prototype]] can be obtained as Object.getPrototypeOf(o).

The built-in Object.prototype can be considered the main root. It is a prototypal ancestor of all non-host objects under usual circumstances which include in particular the following conditions:

Utilizing prototypal inheritance by the class system
Classes are ordered in an inheritance tree via the .$parent (superclass) reference. The inheritance root is c_.Object.

The .$parent map together with the .$class, .$iproto and .$cproto maps uniquely determine the JavaScript prototypal inheritance according to the following diagram.

The diagram shows two classes, A and B, with direct subclass/superclass relationship: B.$parent == A. In addition, b is an instance such that b.$class == B. Full lines denote JavaScript prototypal inheritance between axial nodes. The situation can be demonstrated by the following code.

Notes:

New class creation – Introduction
As already introduced in examples, the standard way to create a new class is by
container.newClass(pathname, parent)
where
The 2x2 diagram
Axial types can be now more properly diagrammatized by the following 2 × 2 table.
Instantial Classial
Prototypal
$iproto
$cproto
Terminal
instances
$class
Note that there are 3 different ways how to split axial nodes into 2 groups:
  1. The vertical split divides the nodes according to which of the two prototypal inheritance trees they belong to:
  2. The horizontal split differentiates prototypes.
  3. The 1 + 3 split is according to the one-to-one correspondence established by .$iproto and .$cproto maps.
The __proto__ reference
The class system ensures that all axial nodes that are not instances have an explicit .__proto__ reference. This means that if the JavaScript implementation does not support built-in __proto__s, the references are created during class creation.

Note that instances are left untouched so that Object.create(x.$iproto) or its backport should create a valid instance of class x (see Vanilla instantiation).

The following example shows some properties of established maps between axial nodes. (The first two equalities show that .__proto__ references between prototypal axial nodes are parallel to .$parent references between classes.)

Erased constructors
The constructor property is fully abandoned in the class system. The following code demonstrates that no axial node contains an own property named constructor (unless explicitly assigned). The ancestors(o, key) function returns the prototype chain of o filtered according to whether the ancestor has an own property named key. Because no (ancestor) axial node has an own property named 'constructor', all classes / instances inherit this property from Object.prototype. Therefore, o.constructor is irrelevant to the class system.
The subclass-of relation
Strict inheritance between classes (i.e. the transitive closure of the .$parent partial map, without the reflexive closure) is also known as the subclass-of relation. Being slightly incorrect in terminology, the class method .isMySubclass() detects the non-strict inheritance, i.e. for a class x and an y, x.isMySubclass(y) detects whether either x === y or y is a subclass of x.
The instance-of relation
The instance-of relation is the composition of the direct-instance-of relation with the self-or-subclass-of relation, i.e. y is instance of x if y is an instance and x a class such that The corresponding class method is x.isMyInstance(y).

Class instantiation

As already demonstrated in previous examples, class instances are created by the NEW() class method.

Notes:

Instance initialization
Similarly to the Ruby programming language, class instances are initialized by the instance method initialize which gets called inside the NEW method.
Vanilla instantiation
If performance is required, uninitialized but otherwise valid class instances can be created using Object.create or its backport, the beget ritual [].

Axial members

Properties of axial nodes are called axial members. We will use the following terminology: As with properties of JavaScript objects in general, we consider both own and inherited axial members.
Member pathname
Similarly to axial nodes, there is a convention for referring to axial members in diagnostics or documentation. The member pathname mpath is composed from the pathname p of the owner axial node o and the member name key, such that The induced ambiguity between own class members and class prototype members is considered unimportant.
Own instance members
The class system does not provide special ways to define, access or modify own instance members. In general, instances can be manipulated by property assignment like plain JavaScript objects.
Prototypal and class members
Prototypal and class members are properties owned by axial nodes of type $iproto, $cproto and $class. The members are defined by mixing definition objects to the respective axial nodes. This is achieved by applications of a _mix method which can be considered as taking the following form
klass._mix(members, axial_type, mix_mode)
where The following table shows 6 basic wrapper methods with parameter-to-method correspondence. Green underline indicates that 'def' mode is preferred to 'patch' mode. Smaller font indicates that the $class axial type is supposed to be used only in rare circumstances.
mix mode axial type → $iproto $cproto $class
'def'
def
self_def
eigen_def
'patch' patch self_patch eigen_patch
The .def() method
The .def() method inserts members into the $iproto node so that they are inherited by instances. The usage pattern is
klass.def(members)
The following is a simple example.

Note: Typically for real applications, the definition object contains functions as members. We use string or numeric members for the sake of brevity.

The .self_def() method
The .self_def() method inserts members into the $cproto node so that they are inherited by classes. The usage pattern is
klass.self_def(members)
The following example corresponds to that of .def().
The .eigen_def() method
The .eigen_def() method inserts members directly into the class – klass.eigen_def(members) makes members own members of klass so that they are NOT inherited by klass subclasses.
Member attributes
Each axial node x of type $iproto, $cproto or $class is equipped with a satelite object, refered to by x.$mattrs, holding member attributes. For an own member m of x named key, the attribute object of m, if defined, is a subobject of x.$mattrs refered to by x.$mattrs[key]. The member m attributes are then properties of x.$mattrs[key] (if any).

Member attributes can be set using the $membattrs key property within the definition object according to the following example.

Note: There are class-system reserved attributes. In particular,

used for fine control of mixture mode, explained later in the Naming conflicts section.

Methods

Methods are considered the most prominent axial members. An object x is regarded as a method if the following are satisfied: This means that methods are named and have unique owners (but not unique receivers). The following example defines several instance methods.
Method cloning
Maintaining the (unique) .$owner reference implies that methods that appear in a definition object have to be cloned.

Note: The current implementation of method cloning uses string evaluation. This precludes following functions from being (properly) cloned:

This is demonstrated in the following examples.
The parent method call (a.k.a. super)
The parent method call (or shortly parent call) can be accomplished using the .pcall method. The default usage pattern is
this.pcall (arguments [, new_arguments])
where

Note: The .pcall() method works both for instances and classes. Thus, there are two methods in fact:

This is one of the few cases where a method is to be defined on both $iproto and $cproto axes.
Explicit parent call
Due to the ECMAScript, 5th Edition, the arguments.callee property is deprecated: it is no longer available in strict mode. If required, the reference to arguments.callee can be avoided using an explicit parent method call. Let callee be the currently called method, obtainable as arguments.callee in non-strict mode. Then the parent method call is performed by
klass.$parent.<axial_type>.<method_name> .apply (this, arguments)
where Note that the three-fold explicitness in specifying the parent method means that the DRY principle is violated 3 times each parent method call.

In the following example,

Parent call dynamics
Parent calls, whether explicit or via .pcall(), dynamically respect prototypal inheritance. They do not rely on any static bindings between overridden methods. In the following example, the method Y#report is inserted between the already defined Z#report and X#report methods.
Method alias
A method m is annotated as an alias method if it has been cloned from a method f such that m.$owner is a (non-strict) prototypal descendant of f.$owner. This makes possible to use alias chaining in combination with the parent method call.

Nesting

Classes are organized in a naming forest called (class) nesting. It has the following constituents: Every class x that is not a nesting root is subject to the following condition: The nesting root of a class x is referred to by x.$nroot.

Class nesting can be considered as a tame part of the (wild) non-hierarchical JavaScript naming system. We can also speak about naming hierarchy but this is a (slight) abuse of terminology, because nesting is only component-wise hierarchical - multiple roots are allowed.

The pathname of a class x is provided by x.pathname() which joins the nesting ancestor names using the dot (.) as separator. However, the following convention is applied for classes x with an anonymous nesting root x.$nroot:

This convention allows for nicer reports in examples.

Notes:

The initial nesting root c_
The only property added to the JavaScript global object (which is window in case of web browsers) by the class system is named c_. This property refers to the initial nesting root.

This named class is the nesting root of all built-in classes. In particular, it is the nesting parent of the inheritance root c_.Object.

Note: The following mnemonic can be used:

The position of c_ in the inheritance tree is shown by the following report: The code also shows that nesting roots are supposed to inherit directly from the built-in class c_.NsRoot.
Registered nesting root
Additional class namespaces can be created by registering named nesting roots.
Namespace nodes
Except for c_.NsRoot, subclasses of the built-in class c_.NsNode are called namespace nodes or nesting nodes. By convention, namespace node names are supposed not to start with uppercase letters A-Z.

As already demonstrated in Paths to axial nodes, the class system supports automatic creation of namespace nodes. For a pathname prefix p to be auto-creatable the following rules recursively apply:

  1. (p does not refer to an existing class.)
  2. The last (simple) name of p does not start with A-Z.
  3. The parent prefix of p is either auto-creatable or refers to an existing namespace node.
Class override
Being class members of their respective nesting parents, classes themselves can be overridden. This can be a convenient way to parameterize the outer class by an inner class.

In the following example, X and Y are outer classes such that Y inherits from X by overriding (redefining) the inner class Reporter. In addition, Reporter is the class of the reporter instance member.

instance member xx.$class
X##reporter X.Reporter
Y##reporter Y.Reporter
Class creation using 'redefine'
As already introduced in the previous example, classes are overridden by using 'redefine' as their parent class specifier. This can lead to multiple classes being created.

For example, suppose we want to redefine classes along the path "Z.A.B.C". The path is normally fully resolved, but in the nesting forest (where property ownership is required), only the prefix "Z" is resolvable – the member class A is (strictly) inherited.

In the before table, the height of gray parts indicates how many times a particular member have been inherited.

before
BC
AB
A
ZA
after
B C
A B1C1
A1B2C2
ZA2B3C3
The after table shows the result after performing _.newClass("Z.A.B.C", 'redefine'). Eight new classes have been created, each class inheriting from the class which appears above in the table. This corresponds to the following code:
Class pathname resolution
Class pathname resolution has following parameters: In a resolution process, each name segment is resolved step by step. The first resolution step for an absolute pathname is a jump to the nesting root. Otherwise a resolution step is a (possibly restricted) JavaScript name lookup (with the exception of looking-up the nesting root name under the nesting root itself – see Nesting root naming loop). The mode parameter prescribes possible resolution restriction.
  1. (A) Strict nesting resolution. In each lookup, the resolved class must be a nesting child of the current referrer.
  2. (B) Normal (non-strict) resolution. The resolution step is valid whenever the name refers to a class.
Nesting root naming loop
Similarly to the Ruby programming language, nesting roots are treated as own parents during the pathname resolution. In Ruby 1.9, it is not possible to create a class with the pathname Object::Object unless the original Object class has been replaced.
newClass in more details
The description of new class creation
klass = container.newClass(pathname, parent)
can be now provided with more details: Resolution of pathname is demonstrated in the following. Resolution of parent is demonstrated in the following.

Mixins

The class system supports mixins. A mixin is a class that serves the purpose of being mixed-in into another class(es) in order to provide additional functionality. Mixins can be considered as class-wrapped definition objects prepared for multiple use.

A class is (recognized as) a mixin if it is a subclass of the built-in class c_.Mixin. This provides perhaps the most important partition of classes into two subsets. We can speak about

In the following example, M is a mixin that provides an instance method named report.
Mixin inclusion structure
The class system maintains a mixin inclusion structure. Classes are equipped with inclusion lists of two kinds. For a class x, The structure is subject to the following conditions:
  1. (0) (Already stated:) Each member of x.$includees is a mixin.
  2. (1) x x.$includees.
  3. (2) y x.$includees implies x y.$includees.
  4. (3) y x.$includers implies x y.$includees.

Note: In general, there might be classes x with an includee y that are not registered in y.$includers. However, this feature is considered internal.

Expanded ancestor list
The .ancestors() class method can be parametrized by a boolean value indicating whether included mixins should be regarded as the reflected ancestors. For a class x, x.ancestors(true) equals (we use Ruby notation for array concatenation / reversion.)
The include operation
Mixin inclusion, as an operation, can be considered to have the following form:
p.include(q, params)
where The operation works as follows (we use Ruby notation for array concatenation / subtraction):
  1. 0. Check inclusion conditions, throw an error if a violation is detected.
  2. 1. Let includers be the list [p] + p.$includers.
  3. 2. For every x in includers, let includees be the (possibly empty) list
  4. 3. For each y from includees, perform a simple inclusion of y into x:
    1. (a) Add x to y.$includers.
    2. (b) Add y to x.$includees.
    3. (c) If x is a non-mixin class, then perform the final mix-in:
      • x._mix (y.$iproto, '$iproto', mix_mode),
      • x._mix (y.$cproto, '$cproto', mix_mode).
      The mix_mode parameter is taken from the params object.
Note that in general, the operation affects two substructures of the data model: This is in contrast to Ruby, where only inclusion structure is affected and includee members (methods and properties) are dynamically inherited without any change in method or property ownership.

In jClassy, mixins are statically mixed-in to classes by modifying includer prototypes.

Deep includers
A mixin with a non-empty $includers list is called a deep includer. This terminology reflects the fact that the following operations performed on a deep includer x are propagated to x.$includers:
The 'deep-inclusion' setting
For the case that deep inclusion is considered risky, the class system provides a means to make deep includer propagation explicit. When the 'deep-inclusion' parameter of the nesting root is set to false, then include_deep resp. $deep : true have to be used to perform the respective operation on a deep includer.

Note: At present, deep inclusion is considered risky / controversial by default:

Inclusion anomalies
As in Ruby, there can be repetitions of mixins along the ancestor path. The following example shows that if M is included into a class X after having been included in X's subclass Y, then it appears twice in the extended ancestor chain of Y.
Member cloning
If requested, members acquired from mixins can be created by cloners. A cloner is a function that is called at final mix-in time. The returned value becomes the includer's member. A member of a definition object is recognized as a cloner provider if it is a (non-function) object containing a $cloner function property:
$cloner : <function>
Using this feature, mixins can provide own clones of objects: The feature can also be used for closure-preserving method cloning:

Naming conflicts

As has already been shown, the class system provides basic resolution of naming conflicts that can arise at the time when members are inserted (mixed-over or mixed-in via _mix) into $iproto, $cproto or $class axial nodes.
Mixture mode
Name conflict resolution is controlled by mixture mode. The following list describes the semantics of recognized mixture mode values, ordered from the most strict to the most tolerant. However, the description does not exactly apply to special members which are treated specially.
  1. 'def-none'. No existing member is allowed to be overridden or overwritten. (The inserted key must not be present between existing member keys, whether own or inherited.)
  2. 'def' – the default. A member is allowed to be overridden iff it is type-compatible () with the inserted member. No member is allowed to be overwritten.
  3. 'def-any'. Any member is allowed to be overridden, no member is allowed to be overwritten.
  4. 'patch'. Except for type-incompatible members, all members are both allowed to be overridden and overwritten.
  5. 'patch-any'. All members are both allowed to be overridden and overwritten.

Notes:

Special members
The following groups of members are treated specially:
Actual mixture mode
Before a particular member is inserted into a $iproto, $cproto or $class node, the actual mixture mode is computed, based on the following: The actual mixture mode is then computed as the most tolerant of mixmode, overmixby and overmixof.
def versus patch
Simply put, while def methods only allow member override, the patch methods allow both override and overwrite.
The $mixmode specifier
In most cases, there is no need to use the $mixmode specifier. The (default) mixture mode of _mixing-in a class definition object is specified by choosing between the def/patch method variants.

However, the specifier at least allows a synoptic demonstration of mixture mode semantics:

The overmixby attribute
The overmixby attribute specifies the member's ability to be overridden / overwritten.
The overmixof attribute
The overmixof attribute specifies the mixin member's ability to override / overwrite target members. It can be considered to be the mixin's mixture mode, applied at the time when the mixin is included into a non-mixin class.
Inclusion check
The current implementation only performs late checks of mixin compatibility (i.e. naming conflicts between mixins that are connected by the includer-includee relation). Possible conflicts are detected at the final mix-in time when a mixin is included into a non-mixin class.

Early checks can be performed explicitly using .checkInclusion().

Miscelaneous features

The 0:0 terminator
As of ECMAScript, 3rd Edition (but not the 5th Edition), object initializers do not allow a trailing-comma after the last <key> : <value> pair. For example, the code
var o = {
  memb1 : 1,
  memb2 : 2,
}
is not legal. This can cause inconveniences in code editation (unless there is a trailing-comma-autocomplete facility on the part of the text editor).

To reduce this problem, definition objects are allowed to contain a 0:0 terminator as a special member. The intended form of use is

c_.test.A.def ({
  memb1 : 1,
  memb2 : 2,
0:0});
so that '0:0});' can be considered a thicker version of the '});' definition closing sequence.
Definition blocks
Class definition methods allow for multiple definition objects being passed as arguments. As a consequence, lines containing just '0:0},{' can be used as separators between definition blocks.
Class modifiers
Class instantiation or subclassing can be restricted by class modifiers. For a single class modifier, the following values are recognized, divided into 2 groups: Only values from different groups can be used in combination.

Class modifiers can be set via the $modifiers reserved key property.

Singleton classes
A singleton class x provides at most one instance, obtained by x.getInstance(). As an experimental feature, a singleton class does not have to be final – as long as x.getInstance() has not been called, a subclass chain can be created. The bottom descendant is then the instantiated class (so that x.getInstance() is still an instance of x, but not necessarily a direct one).
The .inherited() hook
Similarly to Ruby, if a class x has a (not necessarily own) method named 'inherited' then x.inherited(y) is called upon each creation of class y that is a direct subclass of x.

The following example shows a possible application. Each subclass of A is equipped with a configuration object refered to by cfg. A prototypal inheritance of cfg-objects is established, parallel to the class inheritance. (c_.util.object_new (o) can be considered a backport of Object.create(o).)

 

Testing environment

As an application of jClassy, a testing environment, TestEnv, has been created, with the following characteristics: TestEnv evaluates example code and combines the results with the code. The evaluation code is itemized. For each item, the result of its evaluation is reported next to the item code, in the place where traditionally a comment with the expected value would appear. This allows for a shift in code demonstration as in the following example. The example also shows that exceptions thrown during item evaluation are caught and reported.
Each evaluation item can be provided with a pattern of the expected value. In this case, the evaluation result is tested against the pattern. Any detected mismatch is reported.

A detailed example is shown on a separate page.

Notes:

Node.js
The library has passed the following hello-world test in Node.js:

 

API reference

Reserved key properties
Axial node properties
Definition object properties
One-level-deep definition properties
Method properties
c_.Object
The c_.Object class is the inheritance root.
Class methods
Instance methods
c_.NsNode
The c_.NsNode class is the prototype class of namespace (or nesting) nodes. Except the nesting root prototype c_.NsRoot, all subclasses of c_.NsNode are namespace nodes.

c_.NsNode is a static class. As a consequence, namespace nodes cannot be instantiated.

The c_.NsNode class does not define any (public) methods.

c_.NsRoot
The c_.NsRoot class is the prototype class of namespace / nesting roots. All subclasses of c_.NsRoot are namespace/nesting roots.

Each nesting root r is equipped with a configuration object r._config which inherits from c_.NsRoot._config (which in turn inherits from c_.NsRoot._config_proto).

Class methods
c_
The c_ class is the initial nesting root.
Class members
c_.Mixin
c_.Mixin is a static class that serves as a mixin prototype – subclasses of c_.Mixin are mixins.
Class methods
c_.util
c_.util is a namespace node providing core utilities.
Class methods
c_.hash
c_.hash is a namespace node providing utilities for handling JavaScript objects as associative arrays (aka dictionaries, maps or hashes). At present, it should be considered internal or as a namespace placeholder.
c_.aux
c_.aux is an internal class containing most of the class system implementation.
c_.ClassPath
c_.ClassPath is a class that is used for class-path resolution. It should be considered internal.

Summary of built-in classes

Bootstrap sequence

Note: Some built-in classes must be created by internal means. Otherwise a chicken–egg problem of c_ being created after c_.Object would arise.

Inheritance

 

Comparison with other libraries

A comparison with other libraries (JS.Class, qooxdoo, Dojo, MooTools, Ext JS, and CoffeeScript) is provided in a separate document [].

 

References
1&1, qooxdoo, http://qooxdoo.org/
Jeremy Ashkenas, et al., CoffeeScript, http://coffeescript.org/
James Coglan, JS.Class, http://jsclass.jcoglan.com/
Douglas Crockford, JavaScript: The Good Parts, O'Reilly, 2008,
Dojo Foundation, Dojo Toolkit, http://dojotoolkit.org/
Tom Van Cutsem, Mark S. Miller, traits.js – Robust Object Composition and High-integrity Objects for ECMAScript 5, 2011, http://plastic.host.adobe.com/plastic1.pdf
Ryan Dahl & Node.js developers, Node.js: Evented I/O for V8 JavaScript, http://nodejs.org
Ecma International, ECMAScript Language Specification, Edition 5.1, http://www.ecma-international.org/publications/standards/Ecma-262.htm
es-lab, traits.js: Traits for Javascript, http://www.traitsjs.org/
David Flanagan, JavaScript: The Definitive Guide, Sixth Edition, O'Reilly 2011
David Flanagan, Yukihiro Matsumoto, The Ruby Programming Language, O'Reilly 2008
Mozilla Foundation, JavaScript, Mozilla Developer Network, https://developer.mozilla.org/en/JavaScript
Mozilla Foundation, ECMAScript Discussion: arguments.callee in Harmony, mozilla.org Mailing Lists, https://mail.mozilla.org/pipermail/es-discuss/2009-September/thread.html#9851
Mark Joseph A. Obcena, Pro JavaScript with MooTools, Apress, 2010,
Ondřej Pavlata, The Ruby Object Model: Data Structure in Detail, 2011, http://www.atalon.cz/rb-om/ruby-object-model
Ondřej Pavlata, jClassy: Comparison with other libraries, http://jclassy.org/doc/co
John Resig & the jQuery team, jQuery, http://jquery.com
Sencha, Ext JS, http://www.sencha.com/products/extjs/
Malte Ubl, Nickolay Platonov, Jeremy Wall, Joose - A postmodern class system for JavaScript, http://joose.it
Tested environments
The jClassy library has been tested in
Changelog
0.4.2
License
The jClassy library is released under the MIT License. Details can be obtained at http://jclassy.org/license/.