April 2002 Draft
JavaScript 2.0
Core Language
Definitions
|
Friday, April 12, 2002
Definitions are directives that introduce new constants, variables, functions, classes, interfaces, namespaces, and packages. All definitions except those of packages can be preceded by zero or more attributes. There must not be any line breaks between the attributes or after the last attribute.
An attribute is an expression (usually just an identifier) that modifies a definition’s meaning. Attributes can specify
a definition’s scope, namespace, semantics, and other hints. A JavaScript program may also define and subsequently use its
own attributes. Attributes can be qualified identifiers (as long as
they don’t start with a (
) and dotted and function call expressions, but they must be compile-time
constants.
The table below summarizes the predefined attributes.
Category | Attributes | Behavior |
---|---|---|
Scope | extend( C) |
The definition extends class C. |
unit |
Shorthand for extend(Unit) . |
|
Namespace | private internal public |
Makes the definition visible only in the enclosing class’s private namespace (private ),
the enclosing package’s private namespace (internal ), or anywhere (public ). |
Visibility Modifier | enumerable |
This definition can be accessed using the [] operator and seen using a for -in
statement. |
explicit |
This top-level definition is not shared via an import directive. |
|
Evaluation Modifier | compile |
This definition defines a compile-time constant. |
Class Modifier | final |
This class cannot be subclassed. Can be used only on classes. |
dynamic |
Instances of this class can contain dynamic properties. Can be used only on classes. | |
Member Modifier | static constructor operator abstract virtual final |
The definition creates a global member (static ), constructor (constructor ), operator
override (operator ), or instance member (abstract , virtual , or final )
of the enclosing class or interface. If defining an instance member, the definition must (abstract ),
can (virtual ), or cannot (final ) be overridden in subclasses. Can be used only on class
or interface members. |
override override(true) override(false) override(undefined) |
Assertion that the definition overrides (override or override(true) ), may override
(override(undefined) ), or does not override (override(false) ) a member of a superclass. Can
be used only on class or interface members. Controls errors only. |
|
Conditional | true false |
The definition or directive is (true ) or is not (false ) processed. |
Memory Management | weak |
Makes the defined variable be a weak reference. Can be used only on var definitions. |
Miscellaneous | prototype |
Allows a function to access this and be used as a prototype-based constructor. If the function
is an instance member of a class, then the function is visible via the class’s prototype global member
and can take any object in the this parameter. |
unused |
Assertion that the definition is not used. |
Multiple conflicting attributes cannot be used in the same definition, nor can an identical attribute be repeated. Thus,
virtual final private
and public public
are both errors.
A scope attribute describes the scope to which a definition applies. If no scope attribute is given, the definition applies to the innermost enclosing scope except when it is hoisted. If that scope is a class or interface, the definition appears as a member of that class or interface. If that scope is a package, the definition appears as a member of that package.
For compatibility with JavaScript 1.5, in some cases a definition’s scope is hoisted to the innermost regional scope R instead of the innermost scope S. This happens only when all of the conditions below are met:
var
definition.true
.When a definition of n is hosted, the effect is as though n were declared (but not initialized) at the top of the regional scope R.
Definitions not meeting the above criteria are not hoisted. However, an inner non-hoisted definition of name n in scope S within regional scope R prevents n from being referenced or defined in any scope within R but outside S; see definition conflicts.
The extend
attribute takes a parameter C, which should be a compile-time constant expression that
evaluates to a class, and adds the definition as a new member of class C. This allows one to add a method to an
existing class C even if C is in an already defined package P. There are several restrictions:
extend
attribute can only be used on function
, const
, class
,
interface
, or namespace
definitions, or on static var
definitions.
Thus, a class extension cannot add new instance variables to objects of class C; it can, however, add getters
and setters.static
or final
, and it cannot override any existing member.
If the new member is a function
, the default is final
.explicit
namespace. In particular, it means that the new member cannot be public
, because
the public
namespace is predefined by the system and not by any package. The default namespace is internal
,
which makes the new member of C visible only from inside the current package.private
members, and it cannot see P’s internal
members
if P is not the current package. On the other hand, it can see the current package’s internal
members.The following example indicates adding methods to the system class String, using a newly created namespace StringExtension
:
namespace StringExtension; StringExtension extend(String) function scramble():String {...} StringExtension extend(String) function unscramble():String {...} use namespace(StringExtension); var x:String = "abc".scramble();
Once the class extension is evaluated, methods scramble
and unscramble
become available on all
strings in code within the scope of a use namespace(StringExtension)
. There is no possibility of name clashes
with extensions of class String
in other, unrelated packages because the names scramble
and unscramble
only acquire their special meanings when qualified by the namespace StringExtension
.
Unless one desires to export a class extension to other packages, the default namespace internal
generally
works best and simplifies the above example to:
extend(String) { function scramble():String {...} function unscramble():String {...} } var x:String = "abc".scramble();
The unit
attribute is defined as though
compile const unit = extend(Unit);
were evaluated at the top level. Unit
is the class that holds the definitions of unit
names.
Namespace attributes control the definition’s visibility. User-defined attributes provide a finer grain of visibility control.
Every package P has a predefined, anonymous namespace PackageInternalP. That
namespace is attached to all definitions with the internal
attribute in that package. Package P’s
scope includes an implicit use namespace(
PackageInternalP)
definition around the package that grants access to these definitions from within the package only.
Every class C has a predefined, anonymous namespace ClassInternalC. That namespace
is attached to all definitions with the private
attribute in that class. Class C’s scope includes
an implicit use namespace(
ClassInternalC)
definition around
the class that grants access to these definitions from within that class only. private
can only be used inside
a class.
Namespace attributes, including user-defined namespaces, are additive; if several are given for a definition, then that
definition is put into each of the designated namespaces. Thus, a single definition may define a name in two or more namespaces
namespace1 and namespace2 by listing the namespaces
as attributes: namespace1 namespace2 var x
.
Such multiple definitions are aliases of each other; there is only one storage location x
.
A definition of a name id is always put into the namespaces explicitly specified in the definition’s attributes. In addition, the definition may be placed in additional namespaces according to the rules below:
public
namespace.Definitions of top-level entities in a package can only be placed in the namespace public
or in namespaces
(including internal
) defined within that package. See rationale.
Visibility modifier attributes control the definition’s visibility in several special cases.
An indexable definition can be accessed using the default []
operator. A nonindexable definition cannot
be accessed using this operator. Non-public
definitions cannot be indexable.
The default for fixed properties is nonindexable and for dynamic
properties is indexable. One can make a fixed property indexable by making it enumerable
. There is no way
to make a user-defined fixed property indexable but not enumerable
. There is also no way to make a dynamic property
nonindexable.
enumerable
An enumerable
definition can be seen by the for
-in
iteration statement. A non-enumerable
definition cannot be seen by such a statement. enumerable
implies indexable.
The default for fixed properties is non-enumerable
and for dynamic
properties is enumerable
. There is no way to make a user-defined dynamic property non-enumerable
.
explicit
explicit
is used to add definitions to a package P without having them conflict with definitions
in other packages that import package P. explicit
prevents the definition from being accessed as a
top-level variable when a package is imported. The definition can still be accessed as a property
of an object. For example,
package My.P1 {
const c1 = 5;
explicit const c2 = 7;
}
package My.P2 {
import P = My.P1; //
Imports My.P1
without qualification
c1; //
OK; evaluates to 5
c2; //
Error: c2
not defined because explicit
variables are not shared
P.c2; //
OK: explicit
properties are visible
}
compile
If the compile
attribute is given on a const
definition of C, then C’s
value must be a compile-time constant, and C itself will be
considered to be a compile-time constant usable in other compile-time
expressions. When the const
definition is a class member, the compile
attribute implies static
.
The compile
attribute is implicit in class
, interface
, and
namespace
definitions, but may also be specified explicitly on these. The compile
attribute cannot
be given on var
or function
definitions.
A const
definition with the compile
attribute can be accessed by non-compile-time expressions
throughout its scope, including forward-referenced ones. On the other hand, the expression defining the value of a compile const
definition cannot contain forward references.
Class modifier attributes apply to the definition of a class C itself. They may only be used on definitions of classes.
final
If a class C is defined using the final
attribute, then any attempt to define a subclass of C
signals an error.
Note that final
is also a member modifier — when used on a class member,
final
makes that member nonoverridable. If final
is used on a class member that is itself a class,
then it acts like a class modifier instead of a member modifier — it prevents the inner class from being subclassed.
dynamic
Instances of a dynamic
class C or its subclasses can contain dynamic
properties. Other instances cannot contain dynamic properties.
Member modifier attributes modify a class or interface member definition’s semantics with respect to a class hierarchy. They may only be used on a definition of a member M of a class or interface C. They cannot be used on definitions that, for example, create local variables inside a function.
static
, constructor
, operator
, abstract
, virtual
, and
final
The static
(or compile
, which implies static
) attribute makes M be a
global member of C.
The constructor
attribute makes M be a global member of C that constructs new instances
of C.
The operator
attribute makes M be an operator override.
M must be a function
definition using one of the special quoted operator names and signatures.
The abstract
, virtual
, and final
attributes make M be an instance member
of C.
The final
attribute prevents subclasses from defining their own members with the name M (unless
they can’t see this M, in which case they can define an independent M). virtual
and abstract
allow subclasses to override M. In addition, abstract
causes an error to be signaled if an attempt
is made to get or set the value of this class’s M. Note that, if M is abstract
, a subclass
does not have to implement M, but if it doesn’t then it can’t access it.
The default setting for the definition of a member M of a class or interface named C is:
Default Attribute | Kind of Member M |
---|---|
constructor |
function C |
abstract |
function F without a body, where the name F differs from C |
virtual |
function F with a body, where the name F differs from C |
final |
var and const definitions |
static |
class , interface , and namespace definitions |
These attributes may not be used on an export
definition, since export
reuses the original member’s
setting.
Note that final
is also a class modifier — when used on a class member
M, final
prevents M from being subclassed rather than making M be a nonoverridable
instance member of C.
override
The override
attribute reports errors; it has no other effect on the behavior of the program. The override
attribute can only be used on definitions in a class and describes the programmer’s intent to either override or not
override a member from a superclass. If the actual behavior, as defined by the namespace
defaulting and overriding rules, differs, then an error is signaled.
The table below describes the behavior of when a definition of a member M with name id is placed in a class C:
Override attribute given | ||||
---|---|---|---|---|
None | override oroverride(true) |
override(undefined) |
override(false) |
|
M overrides a member in some superclass according to the namespace defaulting and overriding rules | Error | OK | OK | Error |
M does not override anything but there exists an ancestor of C with a member with name id visible at the point of definition of M | Error | Error | OK | OK |
M does not override anything and no ancestor of C has a member with name id visible at the point of definition of M | OK | Error | OK | OK |
The middle case arises for example when an ancestor of a class C defines a public
member named
X
and class C attempts to define a private
member named X
.
An attribute whose value is true
causes the definition or directive to be evaluated normally. An attribute
whose value is false
causes the definition or directive to be skipped; the remaining attributes and the body
of the definition or directive are not evaluated. These are useful for turning definitions on and off based on configuration
settings, such as:
compile const debug = true; compile const nondebug = !debug; debug var nCalls = 0; debug function checkConsistency() {...}
weak
The weak
attribute, which can be used only on var
definitions, makes the defined variable be
a weak reference. If there is no way to reference the variable’s value except via weak references, then the system may,
at its option and at any time, replace the variable’s value with null
. The variable’s type must allow null
.
prototype
The prototype
attribute, which can only be used on a function definition, imposes JavaScript 1.5-like prototype
semantics on that function. If such a function is an instance member then it:
this
in the same manner as JavaScript 1.5;this
parameter instead of just instances of the enclosing class;prototype
global member;prototype
global member;super
within this function.If such a function is not an instance member then it:
this
in the same manner as JavaScript 1.5;By default, the prototype
attribute is set on any unchecked function.
It can be set explicitly on other functions as long as they are not getters, setters, or constructors.
unused
The unused
attribute is a hint that the definition is not referenced anywhere. Referencing the definition
will generate an error.
A user-defined attribute may be defined using a compile const
definition or other definitions that define
constants. All attributes must be compile-time constants. For example:
compile const ipriv = internal static; explicit namespace Version1; explicit namespace Version2; internal compile const Version1and2 = Version1 Version2; class C { ipriv var x; // Same as internal static var x; Version1and2 var simple; // Same as Version1 Version2 var simple; Version2 var complicated; ipriv const a:Array = new Array(10); private var i; for (i = 0; i != 10; i++) a[i] = i; }
A definition extends an activation frame with one or more bindings of qualified
names to values. The bindings are generally visible from the activation frame’s scope.
However, a definition may be invisible or partially invisible inside its scope either because it is shadowed by a more local
definition or it uses a namespace that is not use
d. The name lookup rules
specify the detailed behavior of accessing activation frame bindings.
Each definition or declaration D of a name n has an implicit or explicit scope attribute, which designates a scope S to which the definition applies. Any of S’s activation frames will contain a binding for n as soon as S is entered. That binding starts in the following state:
var
n definition D
without a type or attributes is initialized to the value undefined
upon entry into S.function
n definition D
without types or attributes is initialized to its closure upon entry into S instead of at the time D
is executed.class
or interface
definition D of a name n
in scope S binds n to an opaque value V of type Type
upon entry into S.
V may be used as a type to declare variables, but all other operations are prohibited. V becomes
the actual class or interface object at the time the definition is executed, after which point
instances of V may be created and subclasses may be derived from V.Accessing an activation frame binding in the uninitialized state is an error. If this happens, implementations are encouraged to throw an exception, but may return a value V if they can prove that the definition would assign the value V to the binding.
In general, it is not legal to rebind the same name in the same namespace within an activation frame A. There are a couple exceptions:
var
definitions (which may be hoisted) are allowed in a regional
scope as long as all such definitions have no type and no attributes and strict
mode is not in effect.In addition, if a name n is defined in a scope S inside regional scope R, then it is not permitted to access a definition of n made outside of R from anywhere inside R. Also, two nested scopes S1 and S2 located inside the same regional scope R cannot both define n (S1, S2, and R may be the same scope). In either of these situations, n may be hoisted; if hoisting is not allowed, an error occurs. For example,
const b:Integer = 1; function f(c:Boolean):Integer { const a = b; // Error: b is defined inside the local scope below, which prevents accesses to global b // from anywhere inside the regional scope if (c) { const b:Integer = a + 10; // OK to hide the global b from here. return b; } return a; } function g(c:Boolean):Integer { const b = 3; // OK to hide the global b from here. if (c) { const b:Integer = 10; // Error: can’t redefine b inside the same regional scope. return b; } return b; } function h(c:Boolean):Integer { if (c) { const b:Integer = 10; // OK to hide the global b from here. return b; } else { const b:Integer = 42; // OK: Two independent local definitions of b. return b; } }
To help catch accidental redefinitions, binding a qualified name q::
n
in activation frame A when there is already a binding r::
n in A causes
an error if both namespaces q and r are use
d at the point of the definition of q::
n
and the bindings are not aliases of each other. This prevents the same name from being used for both public
and
private
variables in the same class. Two bindings sharing the same name but with different namespaces may still
be introduced into an activation frame, but only by code that does not use
one or both of the namespaces.
In the example below the comments indicate the scope and namespace of each definition:
var a0; //
Public global variable
internal const a1 = true;//
Package-visible global variable
private var a2; //
Error: private
can only be used inside a class
public var a3 = b1; //
Public global variable
if (a1) {
var b0; //
Local to this block
var b1; //
Hoisted to the global level because of the reference to b1
in the definition of a3
}
if (a1) {
var b0; //
Local to this block
}
public function F() { //
Public global function
var c0; //
Local to this function
internal var c1; //
Local to this function
(may generate a style warning)
public var c2; //
Local to this function
(may generate a style warning)
}
class C { //
Public global class
var e0; //
Public class instance variable
private var e1; //
Class-visible class instance variable
internal var e2; //
Package-visible class instance variable
public var e3; //
Public class instance variable
static var e4; //
Public class-global variable
private static var e5; //
Class-visible class-global variable
internal static var e6;//
Package-visible class-global variable
public static var e7; //
Public class-global variable
if (a1) {
var f0; //
Local to this block
private var f1; //
Local to this block
(may generate a style warning)
}
public function I() {} //
Public class method
}
Sometimes it is useful to add a namespace to a prior definition of a name (perhaps inherited from a superclass) or rename a name inherited from a superclass or imported package. The export definition provides a facility to make an alias of an existing name:
The first FunctionName is the new name. If the second FunctionName is provided then it is the existing name; otherwise, the first FunctionName is also the existing name. If one of the FunctionNames is a getter or setter, then the other one must be a getter or setter, respectively.
A new name is an exact alias of the old name rather than a separate value or entity. Thus, if method A is renamed as method B and B is subsequently overridden by B', then B' really overrides A.
Here are a few examples of definition renaming:
package Light { const red = 1; const green = 2; const blue = 4; const white = red + green + blue; } package Saber { import Light; namespace New; namespace Improved; export white; // Re-export Light’s white New export green; // Re-export Light’s green, but only in the new version class C { internal var size; function color() {} } interface I1 { function get length() {} } interface I2 { function get length() {} } class D extends C implements I1, I2 { var inertia; export size; // Make size public in instances of D export colour = color; // Make alias for British users function mispelled() {}; // Oops! Saber was already released and it’s too late to remove this Improved export misspelled = mispelled; // Users of the improved version can use the good name export length = I2::length; // Resolve name conflict in favor of I2 } }
Should we have a protected
Attribute? It has been omitted for now to keep
the language simple, but there does not appear to be any fundamental reason why it could not be supported. If we do support
it, it might be better to choose the C++ protected
concept (visible only in class and subclasses); the Java
protected
concept (visible in class, subclasses, and the original class’s package) could be represented as
internal
protected
.
Waldemar Horwat Last modified Friday, April 12, 2002 |