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: prototypes as classes

06.27.2011
| 2845 views |
  • submit to reddit

This post describes a proposal for making classes and inheritance simpler in ECMAScript.next: “Prototypes as classes”.

JavaScript prototypal inheritance is hard to understand, especially for people coming from other languages that are used to classes. This post explains that it does not have to be that way: There are plans to simplify things in ECMAScript.next. What’s intriguing about this simplification is that it isn’t a radical departure from current practices, but rather a clarification of them.

This post explains how the new language features work by introducing a hypothetical programming language called “NextScript” that has the new-style classes, but without JavaScript’s legacy baggage. Then we examine how JavaScript currently handles classes and conclude with transferring NextScript’s classes to JavaScript, while remaining compatible with legacy code.

1. NextScript

NextScript is JavaScript without constructor functions. The prototype relationship between objects works like in JavaScript, but there is an operator for setting the prototype.
  • An object can optionally have a prototype, by pointing to another object via the internal property [[Prototype]]. The value of that property can be retrieved via Object.getPrototypeOf(). The prototype object can again have a prototype. The sequence of objects that are each other’s prototype is called the prototype chain.
  • If NextScript cannot find a property in an object, it keeps looking in the prototype, then the prototype’s prototype etc.
  • The prototype operator <| sets the prototype of an object literal:
        var obj = A <| { ... };
    
    After the above declaration, obj has the prototype A.
When you think of a class as being a construct that produces instances then the closest thing to a class that JavaScript has are constructor functions. In contrast, NextScript uses plain old (non-function) objects:
  • Classes: A class C is an object. It contains data that is shared by all instances (mainly methods). A special method called “constructor” sets up the instance-specific data (mainly data properties).
  • Creating instances: Creating a new instance via new C(x,y) does the following:
    • Create a new object o whose prototype is C.
    • Call o.constructor(x,y)
  • Instance check:
        o instanceof C
    
    checks whether C is in the prototype chain of o. It is syntactic sugar for
        C.isPrototypeOf(o)
    
  • Getting the class: The class of an object is its prototype. Example: Are obj1 and obj2 (direct) instances of the same class?
        Object.getPrototypeOf(obj1) === Object.getPrototypeOf(obj2)
    
  • Subclassing: You extend an existing class C by creating a new object D whose prototype is C. If you want to inherit instance properties, you must call C.constructor() from D.constructor().
That is, those objects are very similar to classes in many programming languages (such as Python, Java, or C#), but there is no need for a new construct, an object becomes a class by being the operand of the new operator. Example:
    // Superclass
    var Person = {
        constructor: function (name) {
            this.name = name;
        },
        describe: function() {
            return "Person called "+this.name;
        }
    };

    // Subclass
    var Worker = Person <| {
        constructor: function (name, title) {
            Person.constructor.call(this, name);
            this.title = title;
        },
        describe: function () {
            return Person.describe.call(this)+" ("+this.title+")"; // (*)
        }
    };
Interaction:
    > var john = new Person("John");
    > john.describe()               
    Person called John
    > var jane = new Worker("Jane", "CTO");
    > jane.describe()
    Person called Jane (CTO)
    > jane instanceof Worker
    true
    > jane instanceof Person
    true
The instance jane has the prototype Worker which has the prototype Person. That is, prototypes are used for both the instance-of relationship and the subclass-of relationship. The following diagram shows this prototype chain: 1.1. Super-calls Things become even simpler if we add one more feature: super-calls. They make it easier for an overriding method to call the method it overrides (its super-method). There are two ways of looking up methods:
  • Normal method lookup: obj.m(). To find the method, we look for the first object in the prototype chain of obj that has a property m and invoke that property’s value. During this invocation, this is bound to obj, the object where the search began.
  • Super-method lookup: super.l(). This invocation must be made from a method m. The search for l starts at the super-object of m (the prototype of the object that holds m). During the execution of l, this has the same value as it did in m.
Note that this does not change during a super-call, only the search for the method starts later in the prototype chain. The tricky thing with super-method lookup is to find the super-object. This can be done manually, by directly naming it, as in method Worker.describe() at (*). Or it can be performed automatically, via a language construct of NextScript:
    var Super = {
        foo: ...
    };
    var Sub = Super <| {
        foo: function (x, y) {
            super.foo(x, y); // (**)
        }
    };
The statement at (**) is a super-method lookup and syntactic sugar for
    Object.getPrototypeOf(Sub).foo.call(this, x, y);
Now Worker can be simplified as follows.
    var Worker = Person <| {
        constructor: function (name, title) {
            super.constructor(name);
            this.title = title;
        },
        describe: function () {
            return super.describe()+" ("+this.title+")";
        }
    };

2. Classes in JavaScript (ECMAScript 5)

Let’s look at how classes work in JavaScript.
  • Classes: A class is a constructor function C. C.prototype points to an object with the instance methods. C itself sets up the instance data properties.
  • Creating instances: new C(x,y) does the following:
    • Create a new object o whose prototype is C.prototype.
    • Call C with this pointing to the newly created instance.
  • Instance check: o instanceof C checks whether C.prototype is in the prototype chain of o.
  • Getting the class: via the constructor property. Example: Are obj1 and obj2 (direct) instances of the same class?
        obj1.constructor === obj2.constructor
    
  • Subclassing: You extend an existing class C by creating a new constructor function D. D.prototype has the prototype C.prototype.
Example: We replace the default prototype and thus have to set Worker.prototype.constructor [3].
    // Superclass
    function Person(name) {
        this.name = name;
    }
    Person.prototype.describe = function() {
        return "Person called "+this.name;
    };

    // Subclass
    function Worker(name, title) {
        Person.call(this, name);
        this.title = title;
    }
    Worker.prototype = Object.create(Person.prototype);
    Worker.prototype.constructor = Worker;
    Worker.prototype.describe = function() {
        return Person.prototype.describe.call(this)+" ("+this.title+")";
    };
Note that super-calls are orthogonal to new-style inheritance and would be just as useful in the above code. The same holds for the prototype operator <|.

3. JavaScript versus NextScript

The new approach leads to several simplifications:
  • In JavaScript, an instance o has two relationships with its class C: o is the instance of C and has the prototype C.prototype. In NextScript, there is only the prototype relationship between instance and class. As a result, instanceof becomes easier to understand.
  • Subclassing: In JavaScript, there is an indirection involved in subclassing. To let constructor D subclass constructor C, you must make D.prototype the prototype of C.prototype. In NextScript, you directly connect a subclass to its superclass. As a result, it is also easy to determine whether one class is a subclass of another one.
  • Inheriting class methods: In JavaScript, if a class has a method then a subclass does not inherit it. In NextScript, class methods are automatically inherited, due to the prototype relationship.
All in all, the prototype feels like the natural construct to represent a class.

4. ECMAScript.next: ensuring compatibility with legacy code

  • My initial idea [1] is similar to the hypothetical NextScript as described above.
  • Afterwards, Allen Wirfs-Brock suggested how things could be adapted so that the existing “class protocol” wouldn’t have to be changed [2]. This proposal might make it into ECMAScript.next.

    Given a non-function object C (a “class object”, the prototype as a class):

    • Make sure that C.constructor.prototype points to C. This step is needed for the new operator to work as described below.
    • In the following two cases, treat non-function objects C differently, while not changing the behavior for functions:
      • Interpret new C(...) as syntactic sugar for new C.constructor(...).
      • Interpret o instanceof C as syntactic sugar for C.isPrototypeOf(o)
  • Subclassing old-style classes: It might make sense to let a new-style class inherit from an old-style class. There are two ways to do this:
    • Manually: The subclass extends Superclass.prototype. Constructor chaining and super-method calls should work as expected.
    • Automatically: Extend the prototype operator <| so that, when it encounters a function f as its left-hand side, it makes f.prototype the prototype and not f.
Note that the internal structure is still the same as before. The only difference is that the variable that names the class refers to the prototype and not the constructor.

Improved object literals in ECMAScript.next

The proposal “Object Literal Extensions” has been accepted for ECMAScript.next. It complements the idea of prototypes as classes. Highlights:
  • The prototype operator <|, as introduced via NextScript.
  • Super references, also as introduced above.
  • A shorter notation for methods:
        var obj = {
            data: "abc",
            mymethod(x, y) {
                ...
            }
        };
    
  • Object literal shorthand: The following
        function f(x, y) { return {x, y}; }
    
    is syntactic sugar for
        function f(x, y) { return {x: x, y: y}; }
    

Related reading

  1. Classes: suggestions for improvement [Initial idea to allow new for non-function objects]
  2. Prototypes as the new class declaration [Proposal for ensuring the compatibility with the current protocol]
  3. What’s up with the “constructor” property in JavaScript?
  4. Lightweight JavaScript inheritance APIs [Especially Resig’s Simple Inheritance looks almost like NextScript]

 

From http://www.2ality.com/2011/06/prototypes-as-classes.html

Published at DZone with permission of Axel Rauschmayer, author and DZone MVB.

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