Emulating Programming Constructs in ECMAScript 262-3

This document describes how we intend to support programming constructs such as classes in ECMAScript 262-3.


Draft ECMAScript Edition 4

We would ideally like to be upwards compatible with the direction of Draft ECMAScript Edition 4, (aka "JavaScript 2.0") although the current status makes that difficult.

ECMAScript 4 is not yet standardized, but drafts are available: http://www.mozilla.org/js/language/es4/ It adds classes, optional strict typing, access control modifiers, and getters/setters, among other features. It has been in progress since early 1999.

Meanwhile, prior to ECMAScript 4 standardization, there are several language implementations which possess many features of it:


Emulating Programming Constructs in ECMAScript 262-3

While ECMAScript 262-3 ("edition 3") contains reserved words for many programming constructs (it reserves essentially all Java keywords), it does not actually have explicit support for many of them.

So there must be some programming conventions to emulate those constructs. This is a summary of a variety of constructs indicating how we intend to emulate them in ECMAScript 262-3, and how it is done in other languages.

ConstructEmulationJScript .NETJavaC++
package
("namespace" in ES4 is more akin to XML than C++ meaning)
var burst = {xml: {}};
Limitations: emulation doesn't allow grouping in brackets like C++, and doesn't create a new naming scope.
package System.Web.UI {};
Only allows classes, enums, interfaces directly inside a namespace. "package" is not standardized in Draft ECMAScript 4.
package javax.sql;
Only classes and interfaces can be directly inside a namespace.
namespace foo { ... namespace bar { ... }}
Anything can be defined directly inside a namespace in C++. Namespaces are open, unlike classes.
package variabledo not emulate (like JScript .NET and Java); use class static variables. packages not standardized--namespace bar { int count = 0;}
package constantdo not emulate (like Java); use class/interface constant package foo {enum {PI = 3;}}
enum must be an int type.
- namespace foo {const float PI = 3.14159265;}
package functiondo not emulate (like JScript .NET and Java); use class static methods - - namespace crypt {int md5(char* str, int n, char* buf) { ... } }
aliasing a packagevar Short = a.b.c.Whatever not possible not possible namespace Short = a::b::c::Whatever;
import all of a package's symbolsdo not emulate - import a.b.c.Whatever.* using namespace a::b::c::Whatever;
import a single symbolvar func = a.b.c.Whatever.func import a.b.c.Whatever.func; import a.b.c.Whatever.func; using a::b::c::Whatever::f;
classURI = function(str) { ... };
Only one constructor is possible per class; to support multiple ones the single constructor must look at its arguments or there must be class methods. Note that if a class is going to be a base class, it must support a no-args constructor for purposes of becoming a prototype of a subclass.
class URI { function URI(str: String) { ... } } public class URI { URI(String str) { ... } } class URI { URI(String str) { ... } };
subclassburst.SubClass = function(a) {another.SuperClass.call(this, a); ... }; burst.SubClass.prototype = new another.SuperClass(); burst.SubClass.prototype.constructor = burst.SubClass; package burst; class SubClass extends another.SuperClass { function SubClass(a: int) {super(a); ... } ... }
Can also do: class burst.SubClass extends another.SuperClass { function burst.SubClass(a: int) {super(a); ... } ... }
package burst; public class SubClass extends another.SuperClass { SubClass(int a) {super(a); ... } ... } namespace burst { class SubClass : public another::SuperClass { public: SubClass(int a) : another::SuperClass(a) { ... } ... }; }
The constructor may be defined outside the declaration via SubClass::SubClass(int a) { ... }.
static nested classburst.URI.Authority = function() { ...};
though typically scoping by package is preferred over scoping by class.
class A {static class B { ... } ... } public class A {static class B { ... } ... } class A { class B {}; ... };
inner nested class (container instance is bound)do not emulate (could be done with nested functions). class A {class B { ... } ... } public class A {class B { ... } ... } -
static methodfunction FooBar() { ... }; FooBar.methName = function() { ... };
We could alias FooBar.prototype.methName so that instances could conveniently access also without prefixing, but that might encourage the same habit for variables, and we can't make FooBar.count be the same as FooBar.prototype.count. Also, FooBar.methName is consistent with namespace-qualified methods. Note also that we do not support subclass aliasing, so that SubClass.methName would work.
class FooBar { static function methName(str : String) : String { ... } }
invoke as FooBar.methName, or this.methName, but not obj.methName
public class FooBar { public static String methName(String str) { ... } }
invoke as FooBar.methName, or this.methName or methName within a method body, or obj.methName
class FooBar { public static string methName(string str) { ... } }
invoke as FooBar::methName, or methName within a method, or obj.methName
instance methodFooBar.prototype.methName = function(str) {...};
class FooBar { function methName(str : String) : String { ... } }
default is public access, and all instance methods are virtual.
public class FooBar { public String methName(String str) { ... } }
default is package access, and all instance methods are virtual.
class FooBar { /*virtual*/ string methName(string str) { ... } };
default is public access, and have to explicitly declare virtual methods
class static variableFooBar.count = 0
Not: FooBar.prototype.count = 0
class FooBar { static var instanceCount : int = 0; }
public class FooBar { public static int instanceCount = 0; }
class FooBar { static int instanceCount = 0; }
class constantFooBar.MAX = 100
Same emulation as class variable.
class FooBar { static const MAX : int = 0 ; } public class FooBar { public static final int MAX = 0 ; } class FooBar { const int MAX = 0 ; }
class instance variablefunction FooBar(name) { this.name_ = name; }
no emulation of visibility modifiers.
class FooBar { var name_ : String; function FooBar(name : String) { name_ = name; } ... }
Also getter/setter support (compliant with Draft ECMAScript 4): function get Name() : String {return this.name_ } function set Name(newName : String) {this.name_ = newName;} ... var foo : FooBar = new FooBar(); foo.Name = "Joe";
public class FooBar { public String name_; public FooBar(String name) { name_ = name; } ... } class FooBar { string name_; FooBar(string name) : name_(name) { } ... };
interface/abstract classdo not emulate (we treat an interface as a normal class; we make the implementing class either subclass or unrelated).
We could implement a sort of multiple inheritance where supers other than the first are copied into the subclass prototype object.
interface Foo { function print(); } class Bar extends Baz implements Foo { ... }
abstract class Shape {abstract function print();} class Rect extends Shape { ... }
has both "interface" and "abstract class". Note that Draft ECMAScript 4 does not standardize "interface" or have the "abstract" modifier.
interface Foo { ... } class Bar extends Baz implements Foo { ... }
public abstract class Foo { public abstract void print(); }
has "interface" and "abstract class".
class Foo { ... }; class Bar : public Baz, public Foo { ... }
does not have "interface" or "abstract class" but has "class" and has multiple inheritance. automatically determines it is a ABC from presence a pure virtual method: http://www.parashift.com/c++-faq-lite/abcs.html
interface methodAbstractClass.prototype.methName = function(str) {throw new Error("must implement AbstractClass.methName");};
Note that none of the languages here has a way to mandate static methods.
interface FooBar { function methName(str : String) : String; } interface FooBar { String methName(String str); } class FooBar { virtual string methName(String str) = 0; };
No way to mandate a non-virtual method in C++.
enumdo not emulate enum FLAGS : byte { FLAKY = 1 , GROUND = 2, NEVER = 4 }-enum LEVELS {LOW, MEDIUM, HIGH};
public, private, constdo not emulate (except through symbol naming convention).
with enough work, it can be done; see for example http://www.litotes.demon.co.uk/js_info/private_static.html and http://www.crockford.com/javascript/private.html and http://www.pbwizard.com/Articles/class_inheritance.htm

Like C++ and Draft ECMAScript 4 (but unlike JScript .NET and Java), we do not have a distinct notion of "interface".

We do not currently have an explicit "enum" emulation policy (note that "enum" is native in Draft ECMAScript 4 and JScript .NET).

We currently only have a notion of a *static* nested class. A static nested class is mostly just a scoped name, and its instances have independent lifetime of the containing class. A non-static nested class (sometimes called an "inner class") has private access to a particular outer class instance and cannot be instantiated without the outer class instance. Java and JScript .NET have both static and non-static nested classes. C++ (and C#, if anyone cares) has only static nested classes. It is unclear what Draft ECMAScript 4 plans in this area.

Note that naming and inheritance are orthogonal: a "nested class" has a name within its parent class, and typically is not a subclass of it. (Note that ECMAScript 262-3 has some counterintuitive behavior for name resolution of symbols in the body of nested functions such as scope hoisting, and binding of "this".)


Packages and Namespaces, Oh My!

The current crop of language implementations inspired by Draft ECMAScript4 is particularly ugly in respect to "package" and "namespace".

Here is an abbreviated comparison:
LanguageHas
Draft ECMAScript 4namespace, use namespace
Mozilla Epimetheusstandard + package, import, export
Microsoft JScript .NETonly package and import
Macromedia ActionScript 2.0only import (packages are implicit based on directory names)

While Draft ECMAScript 4 does not standardize on "package", it does introduce "namespace" support. Its "namespace" is not a true scope for symbol resolution, and cannot be hierarchical. It use '::' for qualification, just like C++, but is more like a XML namespace than a C++ namespace. It appears to be motivated in part by a desire to support something like E4X (BEA's proposal for integrating XML support in ECMAScript 4: http://www.oreillynet.com/pub/wlg/2997 and http://dev2dev.bea.com/articles/JSchneider_XML.jsp ). An instance x can have properties whose names are multiple different namespaces (so there might be expressions like a.b::c). Note that namespaces in ECMAScript 4 are also leveraged as a mechanism for package versioning.

In JScript .NET, a "package" acts similarly to Java. It does qualification by '.' (like Java but not like an ECMAScript 4 namespace). Packages can not be nested in one another, but they can be given names containing a '.'. This gives most of the effect of a hierarchy, but note that it is still the case that only a full package name can be imported ("import A.B" does not give access to B.foo, only to A.B.foo). Since ECMAScript 4 static class members are also qualified by '.', name qualification by a package acts lexically in an identical way to name qualification by a class name. (This is like C++, except there qualification is by '::'.) A package directive with the same package name can be used in multiple source files. A package cannot contain functions, only classes, interfaces, and enums.

There are no visibility modifiers in ECMAScript 262-3, such as "public", "protected", "public protected", "private", "internal", etc. JScript .NET supports "public", "internal", "private" as package name attributes, and the ECMAScript 4 draft has the same attributes for namespace names. (Note that in C++ namespaces, all names are effectively public.) JScript .NET supports "public", "protected, "private", "internal" for class members. ECMAScript 4 relies on namespaces for member visibility, though it does provide the "private" keyword as syntactic sugar for a "private" namespace scope.


Using versus Loading

ECMAScript 262-3 does not have standard support for loading of code.

Most command-line ECMAScript 262-3 interpreters have a global load() command. In the browser it can be done by a <script> element (dynamic or static), or by obtaining code text through some means and running eval() on it.

Programming languages vary considerably in these areas:
Language declare package short alias to a package import all of a package's symbols import a particular symbol load module comments
Java package foo.bar; import pack1.pack2; import baz.blah.*; import baz.blah.What; generally not done explicitly, as it is automatic by the java runtime (which may load fewer or more classes than are imported into the namespace). Loading can be done explicitly with the java.lang.ClassLoader.loadClass(). "package" does not have block syntax. Aliasing can just take the last name component.
JScript .NET package foo.bar { ... } - import baz.blah; - loading is mostly implicit as with Java, can be done explicitly via AppDomain.Load only packages can be imported, and package names are always top-level (though may contain dots).
Mozilla JS2 Proposal package foo { } import short = a.b.c; - a package import also causes loading. The spec is confusing and inconsistent in "package" vs. "namespace", probably in an attempt to integrate E4X. See also http://www.mozilla.org/js/simple-packages.html
C++ namespace foo {...namespace bar {...} ... } namespace A = B; using namespace foo::bar; using std::cout; #include <blah.h> at compile time, dlload at run time
Perl5 package Foo::Bar; - use Baz.Blah; use Baz.Blah qw(somefunc); require "Bar/SomeModule.pm", require Bar::SomeModule. (Note that "use" = "require" + "import")
proposed - var BUA = burst.Alg not supported var for_map = burst.Alg.for_map /* no special support */ bu_require("burst.mystuff", ["burst.other"]); ...; bu_loaded("burst.mystuff")

Note that in all cases, loading is done with *modules* which may contain multiple namespaces and objects. Only in Java is there a forced 1-1 mapping between what is loaded and a class. In Perl, there is some convenience if a loaded module contains a particular package, but it is not required.


Implementation

(Examples in this section are intended to be informative of techniques, not indicative of symbol naming in the library.)

Here are two different styles for defining a namespace "Trigonometry":

   var Trigonometry = {
      // namespace constant
      PI: 3.14159,
      // namespace variable
      figures: [],
      // namespace function.
      // For a singleton or namespace, there is no slot cost to defining methods here.
      calculateArea: function(radius) {return 2 * Trigonometry.PI * radius}
   };
   var Trigonometry = {};
   // namespace constant
   Trigonometry.PI = 3.14159;
   // namespace variable
   Trigonometry.figures = [];
   // namespace function
   Trigonometry.calculateArea = function(radius) {return 2 * Trigonometry.PI * radius};

Here is an example style for defining a class "Circle":

   function Ellipse(a, b) {
     this.major_ = a;
     this.minor_ = b;
   }
   function Circle(radius) {
     Ellipse.call(this, radius, radius); // superclass init
     this.radius_ = radius;
     this.getRadius = function() {return this.radius_};
   }
   Circle.prototype = new Ellipse();
   Circle.prototype.constructor = Circle;
   // method
   Circle.prototype.getArea = function() {return Trigonometry.calculateArea(this.radius_)};
   // define a method in two lines to help stupid debuggers
   function Circle_getDiameter() {return this.radius_ * 2}
   Circle.prototype.getDiameter = Circle_getDiameter;

Language Extensions

While it is not true language extension, we do introduce these top-level global functions to assist in writing these constructs:

   bu_inherits

While one could imagine defining other extensions (defclass, defmethod, etc.), their main accomplishment would be signalling intent; they would not shorten code. So for now we do not introduce them.

An example usage would be:

   bu_inherits(Circle, Ellipse)

as the equivalent of:

   Circle.prototype = new Ellipse();
   Circle.prototype.constructor = Circle;