HTML5 Zone is brought to you in partnership with:

Dr. Axel Rauschmayer is a freelance software engineer, blogger and educator, located in Munich, Germany. Axel is a DZone MVB and is not an employee of DZone and has posted 246 posts at DZone. You can read more from them at their website. View Full User Profile

ECMAScript.next: Classes

08.08.2012
| 3350 views |
  • submit to reddit
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 + ")";
    };

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 differs 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);
        }
    

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 configurability

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.
This is exactly like current built-in constructors are set up.

Conclusion

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
Published at DZone with permission of Axel Rauschmayer, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)