July 2000 Draft
JavaScript 2.0
Core Language
Classes
|
Monday, May 8, 2000
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.
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
.
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.
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.
When a ClassDefinition is evaluated, the following steps take place:
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.private
members is constructed and use
d
for the lexical extent of the Block.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.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.
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
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.
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.
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 {
returns
var x:Integer = 3;
function m() {return x}
function n(x) {return x+4}
}
var c = new C;
c.m(); //3
returns
c.n(7); //11
var f:Function = c.m; //f
is a zero-argument function with this
bound to c
returns
f(); //3
returns
c.x = 8;
f(); //8
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 |