2012-07-29

ECMAScript.next: classes

During the July 2012 meeting of TC39 [1], classes have been accepted for ECMAScript.next, the upcoming version of the JavaScript language standard. This blog post explains how those classes work. It is based on Allen Wirfs-Brock’s annotated slides.

Overview

An ECMAScript.next class is syntactic sugar for a constructor – a function, to be invoked via new. That is, class declarations and class expressions are simply more convenient syntax for writing functions. The following is an example of a class Person having a subclass Employee:
    // Supertype
    class Person {
        constructor(name) {
            this.name = name;
        }
        describe() {
            return "Person called "+this.name;
        }
    }
    // Subtype
    class Employee extends Person {
        constructor(name, title) {
            super.constructor(name);
            this.title = title;
        }
        describe() {
            return super.describe() + " (" + this.title + ")";
        }
    }
This is how you use these classes:
    > let jane = new Employee("Jane", "CTO");

    > jane instanceof Person
    true
    > jane instanceof Employee
    true
    > jane.describe()
    Person called Jane (CTO)
The classes are roughly equivalent to the following code (super has no simple equivalent [2]):
    // Supertype
    function Person(name) {
        this.name = name;
    }
    Person.prototype.describe = function () {
        return "Person called "+this.name;
    };

    // Subtype
    function Employee(name, title) {
        Person.call(this, name);
        this.title = title;
    }
    Employee.prototype = Object.create(Person.prototype);
    Employee.prototype.constructor = Employee;
    Employee.prototype.describe = function () {
        return Person.prototype.describe.call(this)
               + " (" + this.title + ")";
    };
As you can see, the classes are effectively turned inside out: Given a class C, its method constructor becomes a function C and all other methods are added to C.prototype.

Details

Grammar

    ClassDeclaration:
        "class" BindingIdentifier ClassTail
    ClassExpression:
        "class" BindingIdentifier? ClassTail

    ClassTail:
        ClassHeritage? "{" ClassBody? "}"
    ClassHeritage:
        "extends" AssignmentExpression
    ClassBody:
        ClassElement+
    ClassElement:
        MethodDefinition
        ";"
        
    MethodDefinition:
        PropName "(" FormalParamList ")" "{" FuncBody "}"
        "*" PropName "(" FormalParamList ")" "{" FuncBody "}"
        "get" PropName "(" ")" "{" FuncBody "}"
        "set" PropName "(" PropSetParamList ")" "{" FuncBody "}"
Observations:
  • Similar to functions, there are both class declarations and class expressions. Also similarly, the identifier of a class expression is only visible within the expression.
  • The value to be extended can be produced by an arbitrary expression. Which means that you’ll be able to write code such as the following:
        class Foo extends combine(MyMixin, MySuperClass) {}
    
  • A class body can only contain methods, no data properties. Prototypes having data properties is generally considered an anti-pattern, so this just enforces a best practice.
  • Semicolons are allowed between methods.
  • A method is a generator [3] if its name is prefixed with an asterisk (*). Such a method is translated to a property whose value is a generator function.

Various checks and features

  • Error checks: the class name cannot be eval or arguments; duplicate class element names are not allowed; the name constructor can only be used for a normal method, not for a getter, a setter or a generator method.
  • Class initialization is not hoisted:
        new Bar(); // runtime error
        class Bar {}
    
    That is usually not a problem, because you can still refer to the class everywhere. You just have to wait until the class definition has been evaluated, before you can use it:
        function useBar() {
            new Bar();
        }
        useBar(); // error
        class Bar {}
        useBar(); // OK
    
  • Instance methods cannot be used as constructors:
        class C {
            m() {}
        }
        new C.prototype.m(); // TypeError
    
    That shows that we are heading towards specialization: Whereas previously, the roles constructor, method and non-method function were all taken on by functions, ECMAScript.next will have a dedicated syntactic construct for each role:
    1. Constructors are created by classes.
    2. Non-method functions are created by arrow functions [4].
    3. Methods are created by method definitions (inside classes and object literals).
    Each of these syntactic constructs produces functions, but ones that differ slightly from those that are produced by function () {}: #1 functions have properties with different attributes (see below, prototype is frozen, etc.). #2 functions have a bound this. #3 functions have additional data to enable the use of super [2].
  • If there is no method constructor then the default is:
        constructor(...args) {
            super(...args);
        }
    
  • If you call a class as a function (without specifying this) then this will be undefined. That’s also how functions as constructors work right now (in strict mode).

Extending

Rules for extending:
  • Don’t extend: class Foo {}
    • The prototype of Foo is Function.prototype (as for all functions).
    • The prototype of Foo.prototype is Object.prototype.
    That is the same as for functions. Note that the above is not equivalent to class Foo extends Object, for the only reason that you normally want to avoid Foo inheriting methods such as Object.create().
  • Extend null: class Foo extends null {}
    • The prototype of Foo is Function.prototype.
    • The prototype of Foo.prototype is null.
    Prevent the methods of Object.prototype from being available to instances of Foo.
  • Extend a constructor: class Foo extends SomeClass
    • The prototype of Foo is SomeClass.
    • The prototype of Foo.prototype is SomeClass.prototype.
    Therefore, class methods are inherited, too. For example: If there is a method SomeClass.bar() then that method is also available via Foo.bar(). That is how CoffeeScript implements inheritance [5].
  • Extend a non-constructor: class Foo extends someObject
    • The prototype of Foo is Function.prototype
    • The prototype of Foo.prototype is someObject
Error checks: The extends value must either be an object or null. If it is a constructor then that constructor’s prototype must be either an object or null.

Mutability and property attributes

Class declarations create (mutable) let bindings. For a given class Foo:
  • Foo.prototype is neither writeable, nor configurable, nor enumerable.
  • Foo.constructor is writeable and configurable, but not enumerable.
  • Foo.prototype.* methods are writable and configurable, but not enumerable. Making them writable allows for dynamic patching. Getters and setters are enumerable (because they are similar to data properties).
This is exactly like current built-in constructors are set up.

Conclusion

When will you be able to use classes?

The ECMAScript.next standard will be finished by the end of 2013. You can expect many pieces (especially classes!) to be prototyped in JavaScript engines well before then. As a rough guess, I’d say that JavaScript engines will fully support the standard sometime during 2014. The new standard becoming dominant will take a few years, just as you only now can slowly rely on ECMAScript 5 being there. Until then, there will be compilers (such as Traceur) to compile ECMAScript.next to, say, ECMAScript 3. That is, on legacy JavaScript engines, ECMAScript.next might become similar to CoffeeScript: A more powerful language (than supported by the engine) that compiles to JavaScript.

Advantages of classes

I used to oppose classes and prefer object exemplars [6]. But given the constraint of not breaking legacy code, I’m now glad that they got accepted for ECMAScript.next. The three main benefits of classes are:
  • Classes will make it easier for newcomers to get started with JavaScript.
  • Classes will make subtyping easier for experienced JavaScript programmers. No more helper APIs such as [7].
  • Classes will help make code more portable between frameworks. Currently, many frameworks implement their own inheritance API, which makes it more difficult to reuse code.
Otherwise, nothing much changes, we still have the same old functions under the hood.

References

  1. ECMAScript: ES.next versus ES 6 versus ES Harmony
  2. A closer look at super-references in JavaScript and ECMAScript.next
  3. Asynchronous programming and continuation-passing style in JavaScript
  4. ECMAScript.next: arrow functions and method definitions
  5. Translating CoffeeScript classes to JavaScript
  6. Prototypes as classes – an introduction to JavaScript inheritance
  7. Lightweight JavaScript inheritance APIs

18 comments:

Considerate said...

Regarding the default constructor… Will it never return anything?

BrandonBenvie said...

Just playing around the other day, I came up with a pretty elegant and simple implementation of super, as well as private/protected state that is shareable between classes of a type. http://bbenvie.com/articles/2012-07-25/JavaScript-Classes-with-private-protected-and-super

Axel Rauschmayer said...

It does not need to. The returned value is the initialized instance! Look at function Person above – it doesn’t return anything, either. Examine how it corresponds to method "constructor" of class Person.

Divan Visagie said...

Why the extends keyword , why why why? whats wrong with ":"

Patrick Mueller said...

in the first example, the contructor and describe methods are separated with a COMMA. I threw up a little in my mouth when I saw that. Later, you mention semicolons can be optionally used to separate methods. Tell me the comma was a mistake on your part. PLEASE.

Bart van der Schoor said...

Feels like they're re-inventing ActionScript 2.0

Next up: optional data types, then move to required data types and default un-dynamic instances and we've arrived as AS3! :D

Axel Rauschmayer said...

Yes that was a typo, thanks for reporting it. Fixed.

Axel Rauschmayer said...

The double colon (::) will be used for guards in the future, so `extends` makes sure that there isn’t too much punctuation in the language. Furthermore, JavaScript has already followed Java syntax to a great extent. Doing the same here does make sense.

[1] http://wiki.ecmascript.org/doku.php?id=strawman:guards

Axel Rauschmayer said...

There is indeed very little new under the sun in programming languages. The main challenge for JavaScript is to gently(!) evolve the language while keeping backward compatibility.

Considerate said...

Thank you. I really shouldn't comment on your blog with stupid comments like this after only reading it late at night.

I'm really looking forward to next version of EcmaScript. So many things are getting better in my opinion.

Elliot Geno said...

What about public, private and protected keywords for classes? That makes for solid code. Also implementing similar to extending classes via interfaces. These are all very good for working on a larger team! Seems like JavaScript could aim a lot higher in this revision instead of taking baby steps. We could have used these years ago!

Axel Rauschmayer said...

Coming to JavaScript from Java, I know what you mean. But JavaScript
works a bit differently and surprisingly often, that’s for the better.
Examples: the ability to directly create objects; the close integration
with JSON databases such as MongoDB and CouchDB; the ability to
dynamically change objects. ECMAScript.next will have private names that
will allow for a lot of flexibility regarding privacy:
http://www.2ality.com/2012/03/private-data.html



Checking for compliance with contracts (such as interfaces) will take longer than ECMAScript.next, but is being worked on. The baby steps w.r.t. JavaScript’s evolution make sense, because many things have to be taken into consideration and with web languages, older versions stay around *much* longer than with traditional languages. You want to give JavaScript the advantages of Java, without losing JavaScript’s advantages or distorting its nature. Private names are one example of thinking differently, with a very beneficial outcome.

Elliot Geno said...

What about Dynamic classes?


Also, about the baby steps. When ActionScript went from AS2 to AS3, it was a complete overhaul... and they are about to do it again for AS4 (ASNext) but not nearly as drastic... Not even sure if they will have a new VM for that one.


What was interesting about Adobe's (at the time Macromedia's) approach was to include both VMs in the player. That way if you have legacy content, it can still be viewed. But if you want to make new content, and the market penetration was viable, you could target brand new features. It was an elegant solution to satisfy both needs. Move forward, but don't alienate.


What if the entire web started over from scratch? What if server-side, DOM, JS, CSS all became one language? (Obviously classes/architecture will help separate concerns.) And if you wrote your web application in this new format, it would obviously require a new browser that included both VMs.


It seems to me, that so much of the web is based on decisions of the past and it limits our future. If we were to have a new "browser spec" that implemented EVERYTHING in one cohesive language. We'd see some major speed enhancements and reset the notions of what to expect from our browser manufacturers.

Axel Rauschmayer said...

I fully understand the appeal of starting from scratch, of cleaning things up. However, I think we can still get a lot of mileage out of improving what we currently have. That means that we won’t fragment the broad coalition that is currently behind HTML5 (including JavaScript) and that all of the JS code on the web will (mostly) be a single code base – no diverging dialects. Furthermore, starting from scratch usually means: you avoid old mistakes, but you also make new ones. A lot of effort has gone into the current technologies. Replicating all of that effort is a lot of work! Brendan Eich sometimes uses the term “worse is better” for this “let’s build on what we already have” approach.

Axel Rauschmayer said...

No worries, you are among friends.

Elliot Geno said...

I don't necessarily see why there can't be a new siloed concept of how the web could work. If it turns out to be a success, great! If not, the other "VM" will move merrily along it's way. I know it would never happen. Most people dismiss the idea as impossible right away. But it's still a pretty utopian/ideal situation.


Haxe is doing a nice job of cross-compiling, but essentially the are just trying to provide one language that rules them all.


Can you imagine the efficiencies if everything was both written and compiled to the same language?

Axel Rauschmayer said...

I’m not saying that a “new siloed concept of how the web could work” can’t possibly succeed. Dart is one such attempt. But: It’s a lot of work, it’s hard to beat the web when it comes to user and developer adoption and you will do many things better, but also some things worse. Examples of technologies that tried, but always remained much less popular than the web and always felt alien inside it: Flash (which was and still is quite successful, but now ever so slowly being replaced by HTML5), Silverlight, Java Applets. Every new effort will have to compete with constantly improving web technologies.

“Can you imagine the efficiencies if everything was both written and compiled to the same language?”
That’s what Node.js does for JavaScript. I’ve also always tremendously liked GWT. I also expect that there’ll be solutions that compile ECMAScript.next to, say, ECMAScript 3 for legacy browsers.

Many good things are coming up on the web: ECMAScript.next, DOM improvements, CSS Grid Layout [1]. I think it’s more practical to look forward to them and put up with some of the quirks than wait for perfection somewhere else.

[1] http://www.2ality.com/2012/03/css-grid-layout-firefox.html

Elliot Geno said...

What are your thoughts on TypeScript? I feel slighted coming from an ActionScript background. AS3 was supposed to be ECMAScript next until Microsoft swore to never adopt it. Tamarin would have essentially be a ubiquitous tried and true engine for JS, but Microsoft blocked Macromedia/Adobe from succeeding.


A couple years pass until most everyone forgets the incident. Then Microsoft rolls their own version of what they think ECMAScript should be? Sounds like Microsoft was being a pouty child... "I'm going to go play all by myself!"

Web Analytics