July 2000 Draft
JavaScript 2.0
Core Language
Classes
previousupnext

Monday, May 8, 2000

Class Definitions

Classes are defined using the class keyword. Limited classes can also be defined via JavaScript 1.5-style functions, but doing so is discouraged for new code.

ClassDefinition 
   class QualifiedIdentifier Superclass ImplementsList Block
|  class QualifiedIdentifier Semicolon
Superclass 
   «empty»
|  extends TypeExpressionallowIn
ImplementsList 
   «empty»
|  implements TypeExpressionList
TypeExpressionList 
   TypeExpressionallowIn
|  TypeExpressionList , TypeExpressionallowIn

Like other definitions and declarations, a class definition or declaration may be preceded by one or more attributes, which affect the class's scope, namespace, and semantics. Every class is also a value and has type Type.

Class Declarations

A class definition not followed by a Block is called a class declaration. A class declaration indicates that the class will be defined later in the same scope. When a class declaration is executed, it defines its QualifiedIdentifier as a new class. That QualifiedIdentifier may be used in subsequent type expressions, but instances of the class may not be created until the class's definition is fully executed. Until then, the only things known about the new class are that null is a member of it and that undefined coerces to null when assigned to a variable of the new class.

For example:

var x:C;           // Error: C not declared
function f(a:C):C; // Error: C not declared

class C;           // C is declared

var y:C;           // OK; y is null
y;                 // null
y = 5;             // Error: Can't write y until C is defined
function f(a:C):C {return a} // OK
new C;             // Error: Can't instantiate C until C is defined

class D extends C {} // Error: Can't subclass C until C is defined

class C {
  C c = new C;     // Error: Can't instantiate C until C is defined
  C d;             // OK
}                  // C is defined at this point

new C;             // OK
y;                 // null

Every class definition at the top level of a block that defines a class with an unqualified name n also generates an implicit class declaration of n at the top of that block. This helps with defining mutually recursive classes by eliminating the need to manually predeclare classes in most cases.

Superclasses

A class may have at most one superclass specified by its extends clause. If omitted, the superclass defaults to Object. A class may also implement one or more interfaces, as specified in the implements clause. The TypeExpressions in both clauses are evaluated at the time the class definition or declaration is executed, before beginning the execution of the Block.

A class is a subtype of its superclass, but it is not a subtype of the interfaces it implements. See the interfaces page for more details.

Body

When a ClassDefinition is evaluated, the following steps take place:

  1. If a declaration has been previously evaluated for this QualifiedIdentifier, let t be that declaration's type; otherwise, a new type t is created and the class's QualifiedIdentifier is bound to the constant t.
  2. The TypeExpressions in the extends and/or implements clauses are evaluated. t is made a subtype of its superclass. t is also annotated as implementing the given interfaces, if any. Any static members of t's superclasses are also defined as properties of the object t.
  3. A new, anonymous namespace for holding the class's private members is constructed and used for the lexical extent of the Block.
  4. Block is evaluated. Any static members defined for Block's top-level scope are added as properties of the object t as they are being defined; these may hide static members inherited from superclasses.
  5. If Block is evaluated successfully (without throwing out an exception), all instance members defined for Block's top-level scope (along with those inherited from superclasses) are collected to make a template for creating instances of type t.

A ClassDefinition's Block is evaluated just like any other Block, so it can contain expressions, statements, loops, etc. Such statements that do not contain declarations do not contribute members to the class being declared, but they are evaluated when the class is declared.

Static Members

A class C's static member id becomes a property of the class object C. This member can be accessed using the expression C.id. static members are inherited from superclasses and superinterfaces. In the case of a conflict between a superclass and a superinterface, the superclass prevails. In the case of a conflict between two superinterfaces, neither is preferred and the lookup either has to be done directly on one of the superinterfaces or qualified by the name of the superinterface.

Inherited static variables have only one global value, not one value per subclass. For example, if class C has a static variable v and class D inherits from C, then v can be read or written either as C.v or as D.v; it's the same variable rather than two separate variables.

Here is an example of these rules:

class C {
  static var v = "Cv";
  static var x = "Cx";
  static var y = "Cy";
  static var z = "Cz";
}

interface A {
  static var x = "Ax";
  static var i = "Ai";
  static var j = "Aj";
}

interface B {
  static var x = "Bx";
  static var y = "By";
  static var j = "Bj";
}

class D extends C implements A, B {
  static var v = "Dv";
}

C.v;    // returns "Cv"
C.x;    // returns "Cx"
C.y;    // returns "Cy"
C.z;    // returns "Cz"
A.x;    // returns "Ax"
B.y;    // returns "By"

D.v;    // returns "Dv"
D.x;    // returns "Cx" (superclass preferred over "Ax" or "Bx")
D.y;    // returns "Cy" (superclass preferred over "By")
D.z;    // returns "Cz"
D.i;    // returns "Ai"
D.j;    // error because of ambiguity: "Aj" or "Bj"?
D.A::j; // returns "Aj"
D.B::j; // returns "Bj"
D.A::x; // returns "Ax"
D.A::i; // returns "Ai"

D.x = 5;
C.x;    // returns 5 (same variable)
C.v = 7;
D.v;    // returns "Dv" (different variables)
C.v;    // returns 7

Constructors

A constructor is a function that creates a new instance of a class C. Constructors are defined using the keyword constructor instead of function. A constructor is usually given the same name as its class, in which case the constructor is known as a default constructor and can be called as new C. If a constructor has a different name, it is invoked as though it were a static function of the class:

class C {
  var a:String;

  constructor C(p:String) {this.a = "New "+p}
  constructor make(p:String) {this.a = "Make "+p}
  static function obtain(p:String):C {return new C(p)}
}

var c:C = new C("one");
var d:C = C.make("two");
var e:C = C.obtain("three");

c.a;     // Returns "New one"
d.a;     // Returns "Make two"
e.a;     // Returns "New three"

A constructor can refer to its class's instance variables via this, but it may not assign to this itself. A constructor should not return a value with a return statement; the newly created object is returned automatically. A constructor's return type is usually omitted, but, if present, it must be its class type.

A contructor for a subclass can call its superclass's constructor as though it were a method of this: this.super::C(args) or this.super::n(args) to call a named constructor n. Such a call can only be made before the first reference to this in the constructor; if a constructor fails to call its superclass's constructor then the superclass's default constructor is called automatically before the first reference to this or before the constructor returns, whichever comes first.

The above syntax for calling a superclass's constructor can only be used from within an immediate subclass's constructor. Only constructors can be called this way; a static function that happens to return an instance of its class (such as obtain above) is not a constructor.

If a class does not define any constructors, an empty default constructor is automatically defined; that constructor merely calls the superclass's default constructor with no arguments.

Note that a constructor always returns a new instance. On the other hand, a static function can return an existing instance of its class. It is possible to define a static function with the same name as its class C; such a function looks like a constructor to the outside (it can be called as new C) but can hand out existing instances of its class. However, subclasses will see that C is not a constructor in such a class because the subclasses will not be able to invoke this.super::C in their constructors.

Instance Members

A class C's instance member id becomes a property of instances of C. If c is an instance of C, then the member can be accessed using the expression c.id. instance members are inherited from superclasses and superinterfaces. In the case of a conflict between a superclass and a superinterface, the superclass prevails. In the case of a conflict between two superinterfaces, neither is preferred and the lookup has to be qualified by the name of the superinterface.

Methods

An instance function member of a class is called a method. A method may use this to refer to the object on which it was called. The value of this will always be an instance of the class or one of its subclasses. A method may not change the value of this.

A method is not in itself a value and has no type. There is no way to extract an undispatched method from a class. The . operator produces a function (more specifically, a closure) that is already dispatched and has this bound to the left operand of the . operator.

A method is called by combining the . operator with a function call. For example:

class C {
  var x:Integer = 3;
  function m() {return x}
  function n(x) {return x+4}
}

var c = new C;
c.m();                 //
returns 3
c.n(7);                //
returns 11
var f:Function = c.m;  //
f is a zero-argument function with this bound to c
f();                   //
returns 3
c.x = 8;
f();                   //
returns 8

Overriding

A class C may override a method m defined in its superclass s. To do this, C should define a method m' with the same name as m and use the override or mayOverride keyword in the definition of m'. Overriding a method without using the override or mayOverride keyword or using the override keyword when not overriding a method results in an error intended to catch misspelled method names.

If method m is volatile, the overriding method m' does not have to have the same number or type of parameters or result type as the overridden method m. On the other hand, if method m is virtual, then m' must be defined with the same argument types and result type, but it may have different default values for optional arguments.

A final method cannot be overridden (or further overridden) in the subclasses in which it is visible.

By default method m' is put in the same namespace as method m. If the definition of m' specifies an explicit namespace N, then m' is put in both N and the namespace of m.


Waldemar Horwat
Last modified Monday, May 8, 2000
previousupnext