April 2002 Draft
JavaScript 2.0
Core Language
Classes
previousupnext

Wednesday, February 20, 2002

Class Definitions

Classes are defined using the class keyword. Limited classes can also be defined via functions with the prototype attribute set, but doing so is discouraged for new code.

ClassDefinition  class Identifier Inheritance Block
Inheritance 
   «empty»
|  extends TypeExpressionallowIn
|  implements TypeExpressionList
|  extends TypeExpressionallowIn implements TypeExpressionList

Like other definitions, a class definition 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.

Superclasses

A class may have at most one superclass specified by its extends clause. If omitted, the superclass defaults to Object. The superclass TypeExpression is evaluated at the time the class definition is executed, before beginning the execution of the Block.

A class may also implement one or more interfaces, as specified in the implements clause. These TypeExpressions are also evaluated at the time the class definition is executed.

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. Create a new type t and bind the class’s QualifiedIdentifier to the constant t.
  2. The TypeExpression, if any, in the extends clause is evaluated, and t is made a subtype of its superclass. Any static members of t’s superclass are also defined as properties of the object t.
  3. The TypeExpressions, if any, in the implements clause are evaluated, and t is annotated as implementing the given interfaces.
  4. A new, anonymous namespace for holding the class’s private members is constructed and used for the lexical extent of the Block.
  5. Block is evaluated using a new activation frame initialized with alias bindings for all most derived global members of the superclass and superinterfaces. Any static and constructor members defined for Block’s activation frame are added as properties of the object t as they are being defined; these may hide static members inherited from superclasses.
  6. 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.

Instance Members

A class C’s instance member id becomes a separate property of each instance of C. If c is an instance of C, then such a property can be accessed using the expression c.id. Instance members are inherited from the superclass and the superinterfaces, if any. 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.

The initializers for var or const instance members are not evaluated at the time the class is created. Instead, these initializers run each time an instance of the class is created. These initializers may not, however, access any of the constructors’ arguments.

Methods

A function instance member 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 attribute in the definition of m'. Overriding a method without using the override or mayOverride attribute or using the override attribute when not overriding a method results in an error intended to catch misspelled method names.

The overriding method m' must have all of the required, optional, and rest parameters that the overridden method m has. In addition, m' may add additional optional parameters to the end of its signature just before the rest parameter, if any.

Let p be any parameter other than these additional optional parameters. If m' does not specify a type for p, it inherits the type of p from m. If m' does specify a type for p, it must be the same type as that for p in m. If p is optional in m, then it must also be optional in m' with the same parameter name; however, the default value may differ.

If omitted, the return type of m' defaults to the return type of m. If supplied, the return type of m' must be the same as the return type of m.

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 namespaces as method m. If the definition of m' explicitly specifies a namespace N, then m' is put in both N and the namespace of m.

Method m' may call method m using the super operator: either super.m(args) or super this.m(args).

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 the superclass and the superinterfaces, if any. 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

prototype Member

If a class C has at least one direct member with the prototype attribute, then C has a special global member called prototype. This member is an object o whose properties correspond to the instance members of C. o is not an instance of C. C’s prototype object is considered to be the JavaScript 1.5-style prototype (not to be confused with prototype) of any instance of C. o’s prototype is the prototype of C’s closest ancestor that has a prototype.

Any instance members of C defined with the prototype attribute can be extracted from o and applied just like in JavaScript 1.5. Other instance members of C are also present in o, but it is not possible to extract them directly from o because the getter or setter for an instance member checks that it is applied to an instance of C before granting access, and o is not an instance of C. These instance members are available via instances of C, which inherit them via the prototype chain.

Generic Members

Each instance member o named n of class C (other than members that are setters without a corresponding getter) also causes a global member g named n to be defined in C. That global member is a function that takes an instance i of class C (or one of its subclasses) as the this argument and returns the result of reading i.n, which will be either o or an override of o in a subclass of C. g can be called using either call, apply, or the .() operator. g carries the member’s namespace information with it, so no use check is needed at the time g is called.

Generic members are JavaScript’s analogue to C++’s pointers to a members. In JavaScript 1.5 strings and the [] operator could be used as pointers to members, but they are no longer sufficient because of access controls and namespaces.

Constructors

A constructor is a function that creates a new instance of a class C. Constructors are defined using the attribute constructor. A constructor is usually given the same name as its class, in which case the constructor attribute is optional and 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 function C(p:String) {this.a = "New "+p}      // The attribute constructor is optional here
  constructor function 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.C("two");
var e:C = C.make("three");
var f:C = C.obtain("four");

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

A constructor can refer to its class’s instance variables via this. If a class C inherits from class B, then when B’s constructor is called while creating an instance of C, B’s constructor will be able to call virtual methods of class C on the partially constructed instance. Likewise, B’s constructor could store this into a global variable v and some other function could call a method of C on the partially constructed object v. Class C’s methods can be assured that they are only called on fully initialized instances of C only if neither C nor any of its ancestors contains a constructor that exhibits either of the behaviors above.

A constructor should not return a value with a return statement; the newly created object is returned automatically. A constructor’s return type must be omitted.

A constructor function 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 they will not be able to invoke C’s pseudo-constructor from their constructors.

If a class C does not define the default constructor C.C, the default constructor C.C is automatically defined; that constructor takes optional named arguments corresponding to all of C’s instance variables and uninitialized instance constants as well as any arguments that C’s superclass’s default constructor B.B takes. C.C first calls B.B and then initializes C’s instance variables and uninitialized instance constants using the corresponding named arguments, if any were given. For example, if class C above had no constructor, one could construct an instance of it using either new C or new C(a:"five").

Calling a Superconstructor

Let C be a class and B its superclass. Any constructor C.n must call a constructor B.m or C.m before it accesses this or super or before it returns. The call can be either explicit or implicit; if C.n does not contain any calls to a constructor B.m or C.m along any execution path, then a call to B.B with no arguments is automatically inserted as the first statement of C.n. The constructor C.n does not have to call another constructor if it exits by throwing an exception. C.n may not make more than one constructor call along any potential execution path. Execution paths are determined statically, assuming that any conditional can go either way and any statement can throw an exception.

A constructor C.n can call another constructor using one of the following statements:

super(args) Calls B’s default constructor B.B.
super.m(args)   Calls B’s constructor B.m. m must be a QualifiedIdentifier that names one of B’s constructors.
this(args) Calls C’s default constructor C.C.
this.m(args) Calls C’s constructor C.m. m must be a QualifiedIdentifier that names one of C’s constructors.

The above must be complete statements, not subexpressions of larger expressions. The first of the four forms above is unambiguously a SuperStatement, but the remaining three are parsed as ExpressionStatements. The following rules indicate whether one of these three forms (super.m(args), this(args), or this.m(args)) is treated as an expression or a call to a constructor:

It is not possible to skip class hierarchy levels while constructing an object — if C’s superclass is B and B’s superclass is A, then one of C’s constructors may not directly call one of A’s constructors.


Waldemar Horwat
Last modified Wednesday, February 20, 2002
previousupnext