ECMAScript.next: prototypes as classes
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.
- 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 forC.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().
// 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.
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.
// 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.
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.
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 forfunction f(x, y) { return {x: x, y: y}; }
Related reading
- Classes: suggestions for improvement [Initial idea to allow new for non-function objects]
- Prototypes as the new class declaration [Proposal for ensuring the compatibility with the current protocol]
- What’s up with the “constructor” property in JavaScript?
- Lightweight JavaScript inheritance APIs [Especially Resig’s Simple Inheritance looks almost like NextScript]
From http://www.2ality.com/2011/06/prototypes-as-classes.html
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)





