ECMAScript 4 Netscape Proposal
Core Language
Definitions
|
Thursday, May 22, 2003
Definitions are directives that introduce new constants, variables, functions, classes, namespaces, and packages. All definitions except those of packages can be preceded by zero or more attributes. In non-strict mode 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. An ECMAScript 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 |
---|---|---|
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 seen using a for -in statement. |
explicit |
This top-level definition is not shared via an import directive. |
|
Class Modifier | final |
This class cannot be subclassed. Can be used only on classes. |
dynamic |
Direct instances of this class can contain dynamic properties. Can be used only on classes. | |
Member Modifier | static virtual final |
The definition creates a global member (static ) or instance member (virtual or
final ) of the enclosing class. If defining an instance member, the definition can (virtual )
or cannot (final ) be overridden in subclasses. Can be used only on class 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 members. Controls errors only. |
|
Conditional | true false |
The definition or directive is (true ) or is not (false ) processed. |
Miscellaneous | prototype |
Allows a function to access this and be used as a prototype-based constructor. |
unused |
Assertion that the definition is not used. |
Multiple conflicting attributes cannot be used in the same definition, so virtual final private
is an error. The attributes true
and false
do not conflict. Specifying an attribute more than once
has the same effect as specifying it once.
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:
override(false)
or override(undefined)
attribute is required on the definition if an inherited instance member with
the name id is visible at the point of the definition. This restriction prevents one from accidentally
defining a private
instance member with the same name as a public
instance member in a superclass
(by the rules of member lookup, such a private
member would be shadowed by the existing public
member for all unqualified accesses).public
namespace.Visibility modifier attributes control the definition’s visibility in several special cases.
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
only applies to public
definitions.
The default for dynamic properties and class
properties is enumerable
. The default for instance properties
is non-enumerable
. There is no way to make a user-defined dynamic or class 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
}
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
Direct instances of a dynamic
class C can contain dynamic
properties. Other instances cannot contain dynamic properties. A class is dynamic
if it has the dynamic
attribute or it has a dynamic
ancestor other than Object
.
Member modifier attributes modify a class 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 C. They cannot be used on definitions that, for example, create local variables inside a function.
static
, virtual
, and final
The static
attribute makes M be a global member of C.
The 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
allows
subclasses to override M.
The default setting for the definition of a member M of a class named C is:
Default Attribute | Kind of Member M |
---|---|
none — the function is treated specially as a class constructor | function C |
virtual |
function F where the name F differs from C |
final |
var and const definitions |
none — static attribute must be specified explicitly |
class 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:
const debug = true; const nondebug = !debug; debug var nCalls = 0; debug function checkConsistency() {...}
prototype
The prototype
attribute can only be used on a function. A function with this attribute treats
this
in the same manner as ECMAScript 3 and defines its own prototype-based class as in ECMAScript 3. 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 const
definition or other definitions that define constants.
All attributes must be compile-time constants. For example:
const ipriv = internal static; explicit namespace Version1; explicit namespace Version2; internal 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 applies to the innermost enclosing scope except when it is hoisted. If that scope is a class, the definition appears as a member of that class. If that scope is a package, the definition appears as a member of that package.
For compatibility with ECMAScript 3, 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.
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 applies to some scope S using the rules above. 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
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 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
}
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 Thursday, May 22, 2003 |