Updates – read first:

[2011-11-04] “Myth: JavaScript needs classes” introduces a more concise name for “prototypes as classes”: object exemplars . An exemplar is a factory for instances, constructors are function exemplars .

. An is a factory for instances, constructors are . [2012-01-08] “JavaScript inheritance by example” is a more hands-on introduction, complementing this post.

[2012-07-29] Classes have been accepted for ECMAScript.next.

[2012-10-03] Since this article has been written, it was decided that ECMAScript will have the special property __proto__ instead of the <| operator.

JavaScript’s 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: The proposal “prototypes as classes” is a simplification of classes and inheritance in JavaScript. It might become part of ECMAScript.next, a.k.a. “the next version of JavaScript” (after ECMAScript 5). But there is also a library that allows you to use its features in today’s JavaScript. What’s intriguing about prototypes as classes is that they aren’t a radical departure from current practices, but rather a clarification of them.

Incidentally, this post is also a good introduction to JavaScript inheritance, because the basics are easier to understand with prototypes as classes.

Executive summary: The core idea of “prototypes as classes” is that prototypes are better classes than constructor functions. That is, if you have a class C and create instances via new C() , then C should be the prototype of the instances, not their constructor function. Sect. 3 shows constructor functions as classes side by side with prototypes as classes, which should make it obvious which one is the better choice. Sect. 5 presents a library that lets you use prototypes as classes in today’s JavaScript.

Read on for a gentle introduction to this idea.

Introduction

Sect. 1: Explain prototypes as classes via NextScript. First review the basics of prototypes (which are the same in JavaScript and NextScript). Then explain prototypes as classes in more detail.

Sect. 2: For comparison, show how current JavaScript does classes and subclassing – via constructor functions as classes.

Sect. 3: Show constructor functions as classes side by side with prototypes as classes to explain the benefits of the latter approach.

Sect. 4: ECMAScript.next might get prototypes as classes, in a way that maximizes compatibility with legacy code. ECMAScript.next will also have more powerful object literals, which would benefit prototypes as classes.

Sect. 5: The Proto.js library lets you use prototypes as classes in current JavaScript. Its succinctness is a partial validation of the approach.

Prototypes

The prototype chain: 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 .

An object can optionally have a , by pointing to another object via the internal property . The value of that property can be retrieved via . The prototype object can again have a prototype. The sequence of objects that are each other’s prototype is called the . Property lookup: If NextScript cannot find a property in an object, it keeps looking in the prototype, then the prototype’s prototype etc. While it does so, this always points to the beginning of the prototype chain.

<|

var obj = A <| { foo: 123 };

obj

A

var obj = Object.create(A); obj.foo = 123;

Prototypes as classes are explained by introducing a hypothetical programming language called “NextScript”. That language is exactly like JavaScript, with one difference: Where JavaScript uses the relatively complicated constructor functions to create instances, NextScript uses prototypes to do so. That is, prototypes are classes in NextScript. This post is structured as follows:The only inheritance mechanism that NextScript has is therelationship between two objects. Prototypes work like in JavaScript:NextScript has a newfor specifying the prototype of an object literal [4] After the above assignment,has the prototype. The equivalent in JavaScript is

Prototypes do everything. Both the instance-of relationship and the subclass-of relationship is expressed via the has-prototype relationship in NextScript:

Instance and class, instance-of: the prototype object P of an object o plays the role of a class; o is an instance of P .

the prototype object of an object plays the role of a class; is an instance of . Subclass and superclass, subclass-of: a prototype object P can have a superclass P' by making the object P' its prototype.

Prototypes as classes

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).

A class is an object. It contains data that is shared by all instances (mainly methods). A special method called “ ” sets up the instance-specific data (mainly data properties). Creating instances: Creating a new instance via new C(x,y) desugars to the following ECMAScript 5 code (internally): var o = Object.create(C); o.constructor(x,y); That is, the following happens: Create a new object o whose prototype is C . Call o.constructor(x,y) After these steps, o is an instance of its prototype object C . When looking for a property p via o.p , it should be obvious that both the unique instance data and the shared prototype data can be found, due to how prototypes work. If a method is found in the prototype and executed, the value of the this variable is still o , which enables the method to access the instance data.

Creating a new instance via desugars to the following ECMAScript 5 code (internally): That is, the following happens: After these steps, is an instance of its prototype object . When looking for a property via , it should be obvious that both the unique instance data and the shared prototype data can be found, due to how prototypes work. If a method is found in the prototype and executed, the value of the variable is still , which enables the method to access the instance data. Instance check: o instanceof C checks whether C is in the prototype chain of o . It is syntactic sugar for C.isPrototypeOf(o)

checks whether is in the prototype chain of . It is syntactic sugar for 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)

Person

var Person = { constructor: function (name) { this.name = name; }, describe: function() { return "Person called "+this.name; } };

> var john = new Person("John"); > john.describe() Person called John > john instanceof Person true



All instances of Person have that class as a prototype. All instances ofhave that class as a prototype.

Subclassing

D

C

Inherit prototype properties: D ’s prototype is C .

’s prototype is . Inherit instance properties: D.constructor() calls C.constructor() before setting up its own instance data.

D

C

D

C

D

C

The details of how this works are explained below. When you think of a class as a construct that produces instances, then the closest thing to a class that JavaScript has are constructor functions (Sect. 2). In contrast, NextScript uses plain old (non-function) objects:You can see that while NextScript’s classes are only objects, there is not much difference between them and “real” classes in other programming languages (such as Python, Java, or C#). The following example shows the NextScript classin action.Interaction:A new classextends an existing classin two steps:Thus, we have performed everything that is necessary to makea subclass of: Instances ofwill have their own instance data in addition to’s instance data. And they will have’s methods in addition’s.

Example: Subclass Worker extends superclass Person .

var Worker = Person <| { constructor: function (name, title) { Person.constructor.call(this, name); this.title = title; }, describe: function () { return Person.describe.call(this)+" ("+this.title+")"; // (*) } };

> var jane = new Worker("Jane", "CTO"); > jane.describe() Person called Jane (CTO) > jane instanceof Worker true > jane instanceof Person true

jane

Worker

Person

Super-calls

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.

. To find the method, we look for the first object in the prototype chain of that has a property and invoke that property’s value. During this invocation, is bound to , 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 .

this

Worker.describe()

var Super = { foo: ... }; var Sub = Super <| { foo: function (x, y) { super.foo(x, y); // (**) } };

Object.getPrototypeOf(Sub).foo.call(this, x, y);

Worker

var Worker = Person <| { constructor: function (name, title) { super.constructor(name); this.title = title; }, describe: function () { return super.describe()+" ("+this.title+")"; } };

Classes in JavaScript (ECMAScript 5)

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.

A class is a constructor function . points to an object with the instance methods. 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.

does the following: Instance check: o instanceof C checks whether C.prototype is in the prototype chain of o .

checks whether is in the prototype chain of . Getting the class: via the constructor property. Example: Are obj1 and obj2 (direct) instances of the same class? obj1.constructor === obj2.constructor

via the property. Example: Are and (direct) instances of the same class? Subclassing: A new constructor D extends an existing constructor C in two steps: Inherit prototype properties: Let D.prototype have the prototype C.prototype . Inherit instance properties: Let constructor D call constructor C as a function (no new operator!) and hand in this so that C adds its properties to the new instance of D (without creating a new instance itself).

A new constructor extends an existing constructor in two steps:

Worker.prototype.constructor

// 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+")"; };

<|

Comparing JavaScript and NextScript

Object.create()

Being an instance versus having a prototype: 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. JavaScript: o instanceof C === C.prototype.isPrototypeOf(o) NextScript: o instanceof C === C.isPrototypeOf(o) 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 easier to determine whether one class is a subclass of another one. JavaScript: Sub.prototype = Object.create(Super.prototype) NextScript: Sub = Object.create(Super) Checking for a superclass relationship: In JavaScript, a super-constructor and a sub-constructor are only related via the values of their prototype properties. Prototypes as classes are directly related. JavaScript: Super.prototype .isPrototypeOf(Sub.prototype) NextScript: Super .isPrototypeOf(Sub) Super-calls: When calling an overridden method in a superclass, you access the method in the super-prototype in JavaScript (i.e, not the superclass). JavaScript: Super.prototype.foo.call(this) NextScript: Super.foo.call(this) 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. Instantiation versus initialization: When it comes to creating a new instance, there are two concerns: Instantiation: Create a new instance, give it the proper prototype. Initialization: Set up the instance variables. In JavaScript, a constructor function either plays both roles or just role #2 (when called from a sub-constructor). In NextScript, the method constructor() is only responsible for initialization (it could be renamed to initialize to make that fact more explicit). As a result, initialization chaining in NextScript is conceptually simpler than constructor chaining in JavaScript. Generic methods: To use a generic method, you have to refer to a prototype. The following example shows how to turn the pseudo-array arguments in an array via the slice() method of class Array . JavaScript: Array.prototype.slice.call(arguments) NextScript: Array.slice.call(arguments)

ECMAScript.next: ensuring compatibility with legacy code

Initial idea: My initial idea [1] is similar to the hypothetical NextScript as described above.

My initial idea [1] is similar to the hypothetical NextScript as described above. Ensuring compatibility: 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)

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. 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 Super.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 .

It might make sense to let a new-style class inherit from an old-style class. There are two ways to do this: A competing proposal: Class literals have been proposed for ECMAScript.next. They look much like prototypes-as-classes, but are internally translated to constructor functions. It might be impossible to only have prototypes-as-classes in JavaScript (as that might break too much existing code). If so, then class literals avoid the hassle of having to support both prototypes-as-classes and constructor functions.

Interaction:The instancehas the prototypewhich has the prototype. That is, prototypes are used for both the instance-of relationship and the subclass-of relationship. The following diagram shows this prototype chain:Things become even simpler if we add one more feature: super-calls [4] . They make it easier for an overriding method to call the method it overrides (its). There are two ways of looking up methods:In other words: A super-method call is the same as a-method call, only the search for the property starts later in the property 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 methodat (*). Or it can be performed automatically, via a language construct of NextScript:The statement at (**) is a super-method lookup and syntactic sugar forNowcan be simplified as follows.Let’s look at how classes work in JavaScript.Example: We replace the default prototype and thus have to setNote 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 operatorThe new approach allows you to work more directly with the core inheritance mechanism of JavaScript – prototypes. It thus continues the tradition of Crockford’s prototypal inheritance in ECMAScript 5). Several aspects of JavaScript become conceptually clearer with prototypes as classes/NextScript:If you look at the JavaScript code above, you will notice that, after instantiation, we only need the constructor to access the prototype. This makes it obvious that the prototype should be the class and not the constructor. 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. This should make it clear why the proposal is called “prototypes as classes”. And that it changes relatively little.

Alas, as things stand right now, it is not likely that prototypes as classes will ever make it into JavaScript. My current favorite are class literals that desugar to prototypes-as-classes, e.g.

class Foo extends Bar { ... }

Foo

It will be easier for IDEs to find your classes and support auto-completion etc.

It gives you the option to introduce more inheritance-related features in the future (e.g. traits).

They look familiar to people coming from class-based languages. Prototypes as classes ensure that the syntactic sugar is conceptually very similar to what is going on under the hood.

Improved object literals in ECMAScript.next

The prototype operator <| (borrowed by NextScript above).

(borrowed by NextScript above). Super references (also borrowed by NextScript).

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

A library for current JavaScript

new operator – method new : Instead of new MyClass(...) you write MyClass.new(...) Credit for this idea goes to Irakli Gozalishvili and his prototype-centric inheritance library “selfish”. He comments: I think that the Proto.new(...) form is not a constraint. It’s a feature, as user may redefine their own custom “new”. I wish “new” and “instanceof” operators could be deprecated.

Instead of you write Credit for this idea goes to Irakli Gozalishvili and his prototype-centric inheritance library “selfish”. He comments: <| operator – method extend : Instead of A <| { ... } you write A.extend({ ... }) Subclasses of Proto automatically inherit its class method extend , because Proto is part of their prototype chain.

Instead of you write Subclasses of automatically inherit its class method , because is part of their prototype chain. instanceof operator – method isPrototypeOf : Instead of o instanceof C you write C.isPrototypeOf(o) This is a built-in JavaScript method. One just exploits the fact that the prototype of an instance is also its class.

// To be part of ECMAScript.next if (!Object.getOwnPropertyDescriptors) { Object.getOwnPropertyDescriptors = function (obj) { var descs = {}; Object.getOwnPropertyNames(obj).forEach(function(propName) { descs[propName] = Object.getOwnPropertyDescriptor(obj, propName); }); return descs; }; } /** * The root of all classes that adhere to "the prototypes as classes" protocol. * The neat thing is that the class methods "new" and "extend" are automatically * inherited by subclasses of this class (because Proto is in their prototype chain). */ var Proto = { /** * Class method: create a new instance and let instance method constructor() initialize it. * "this" is the prototype of the new instance. */ new: function () { var instance = Object.create(this); if (instance.constructor) { instance.constructor.apply(instance, arguments); } return instance; }, /** * Class method: subclass "this" (a prototype object used as a class) */ extend: function (subProps) { // We cannot set the prototype of "subProps" // => copy its contents to a new object that has the right prototype var subProto = Object.create(this, Object.getOwnPropertyDescriptors(subProps)); subProto.super = this; // for super-calls return subProto; }, };

// Superclass var Person = Proto.extend({ constructor: function (name) { this.name = name; }, describe: function() { return "Person called "+this.name; } }); // Subclass var Worker = Person.extend({ constructor: function (name, title) { Worker.super.constructor.call(this, name); this.title = title; }, describe: function () { return Worker.super.describe.call(this)+" ("+this.title+")"; } });

var jane = Worker.new("Jane", "CTO"); // normally: new Worker(...) > Worker.isPrototypeOf(jane) // normally: jane instanceof Worker true > jane.describe() 'Person called Jane (CTO)'

Related reading

This would produce a prototype-as-class calledand the “class body” in curly braces would be very similar to an object literal. Class literals give you three benefits:The proposal “ Object Literal Extensions ” has been accepted for ECMAScript.next. It is essential for making “prototypes as classes” easy to use. Highlights:The following code implements “prototypes as classes” in ECMAScript 5 and can be downloaded at proto-js on GitHub. Current JavaScript does not let you do prototypes-as-classes as shown above. Thus, the library uses methods instead of the following three operators (for which you cannot provide a custom implementation):Interaction: