Javascript objects and classes aren’t hard. This whole “prototype” thing is blamed for too much: prototype-based programming isn’t hard. this
is really weird, but prototypes aren’t.
What’s prototype-based programming? It just means every object has a “prototype” and when you look up a property on the object it searches the object, then the object’s prototype, then the prototype’s prototype, and so on. It’s exactly like classes except there’s no special distinction between “instance” and “class”. You don’t have to change paradigms to understand prototypes, you don’t have to drink any Kool Aid to accept the idea. Don’t overthink it, it’s fine.
The problem with Javascript isn’t prototypes, it’s the new
operator. The new
operator is terrible in all ways. But to explain why it is bad, I want to start with the right way to think about prototypes: Object.create().
Object.create
is newer than new
and support isn’t universal (though there’s a polyfill), but it’s much simpler.
Object.create(obj)
creates a new object, with obj
as the new object’s prototype. (There’s a second argument that does fancy things, but let’s ignore that part.) That’s really all you need to know, and you should probably be able to understand it. As an example, this tutorial by Yehuda Katz uses it as the basis for explaining Javascript objects in general.
We can describe new
in terms of Object.create()
:
function new_(constructor /* plus a variable number of arguments */) {
var newObject = Object.create(constructor.prototype);
// This gets the varargs after `constructor`:
var restArgs = Array.prototype.slice.call(arguments, 1);
var result = constructor.apply(newObject, restArgs);
if (typeof result == "object") {
// If the function returns something, ignore newObject
return result;
}
return newObject;
}
Using this, new Constructor(1, 2)
is equivalent to new_(Constructor, 1, 2)
This might seem reasonable, but it has a few problems:
There’s nothing to distinguish constructor functions from normal functions
So there’s nothing to keep you from calling Constructor(1, 2)
— but the results will be bad. If you’ve used Javascript much you’ve probably made this mistake, and added global variables as a result.
If you are not familiar with it, it works like this:
function Constructor(a, b) {
this.a = a;
this.b = b;
}
var obj = Constructor(1, 2);
// Now there is a global object a==1, b==2
// because "this" was bound to the window
// obj is undefined because Constructor didn't return a value
It’s not actually that easy to detect when new
was left off.
You can check if this === window
, but that doesn’t work in Node.js (you’d have to check this === module
). The best way is generally:
function Foo() {
if (! this instanceof Foo) {
return new Foo();
}
}
But then it’s not clear if new
should be used at all, you may very well need to understand named function expressions, and passing arbitrary arguments along is hard).
There’s two ways to define the prototype: awkard or inflexible
The more common way to define the prototype is like this:
function Foo() {
}
Foo.prototype.method = function () {
};
This gets kind of tedious, and you repeat the class name over and over. A shorthand is helpful:
function Foo() {
}
Foo.prototype = {
method: function () {
}
};
But that makes it hard to do subclassing, because you are overwriting the prototype. This keeps you from overriding just the methods you care to in the subclass. Which leads us to…
Clean subclassing is hard to impossible because of the constructor
So here’s how you are supposed to create a class Bar
that subclasses Foo
:
function Bar() {
}
Bar.prototype = new Foo();
Now you can add methods. But can you create a new Foo
object so easily? Usually the constructor will have arguments, maybe even side effects, creating peculiar artifacts in your prototype.
(Of course you could do Bar.prototype = Object.create(Foo.prototype)
— and you probably should — but once you use Object.create()
there’s no reason to go back to new
.)
Prototypes are still okay
Given the problems with new
some people resort to wildly different means of creating object. For instance, the explicit method:
function Point(x, y) {
return {
x: x,
y: y,
add: function (other) {
return Point(this.x + other.x, this.y + other.y);
}
};
}
Or you can avoid this
entirely:
function Point(x, y) {
return {
getX: function () {
return x;
},
getY: function () {
return y;
},
add: function (other) {
return Point(x + other.getX(), y + other.getY());
}
};
}
There’s a wealth of quirky ways to use objects, ad hoc methods, closures, factory functions, and other techniques to create something class-like. But prototypes don’t need all that fixing! These techniques add memory overhead, sometimes performance overhead, and make it harder for other people to read your code. Prototypes don’t need fixing.
Douglas Crockford described some patterns like this (using closures) in Javascript The Good Parts but I think he ultimately has come around to prototypes.
A modest proposal for classes
Lots of libraries include shortcuts for classes. But they often add other features that get in the way of just replacing new
. I will offer my own class constructor, which in my opinion does exactly what it needs to do and nothing more:
function Class(superclass, properties) {
var prototype;
if (! properties) {
// We're creating an object with no superclass
prototype = superclass;
} else {
prototype = Object.create(superclass.prototype);
for (var a in properties) {
if (properties.hasOwnProperty(a) {
prototype[a] = properties[a];
}
}
}
var ClassObject = function () {
var newObject = Object.create(prototype);
if (newObject.constructor) {
newObject.constructor.apply(newObject, arguments);
}
return newObject;
};
ClassObject.prototype = prototype;
return ClassObject;
}
// Use like:
var Point = Class({
constructor: function (x, y) {
this.x = x;
this.y = y;
},
add: function (other) {
return Point(this.x+other.x, this.y+other.y);
}
});
This doesn’t have a special way to call the superclass. Instead you just have to be explicit, for instance you might do:
var Subclass = Class(Superclass, {
method: function (a, b) {
a = Math.abs(a);
return Superclass.prototype.call(this, a, b);
}
});
It’s a little tedious to write out Superclass.prototype.call(...)
, but in my experience these superclass calls don’t happen that often, and trying to be clever is worse than the little bit of extra typing required.
I’ve chosen the name constructor
for the constructor method because it’s the one name already used by Javascript. I prefer this over init
or __init__
or something else adopted from another language.
ES6 will save us all, sometime far in the future
ES6 (EcmaScript 6, the version of Javascript currently in development) has a class statement (this is probably more readable). Unlike some previous proposed versions of Javascript, and some previous proposed class statements, this one seems like it will actually stick. That will resolve most of the problems with our current vague patterns (though it’s unclear to me how it affects new
).
But let’s be honest: most of us will not be able to use this for quite some time (though who knows, with source maps we might be able to compile down to older versions of Javascript without much loss in debuggability). So until then we must manually apply sanity when we write Javascript.
Another improvement coming into availability is "use strict"
— the ability to apply strict behaviors (both in syntax and at runtime) to your code. This allows you to at least avoid the problem of forgetting new
by setting this
to undefined when you leave off new
Comments
I always thought of the "new" operator as meaning a new scope. This way we can interpret the line "var obj = new Operator(1, 2);" as "create a new scope named obj from this function". BTW, I like your class constructor, will definitely use for my new Javascript explorations. :)
It doesn't create a scope, it creates an object which inherits from Operator.prototype and immediately calls the Operator function in the context of this object. ;-)
I'm not really sure about this. I don't think using the new operator is bad practice. Sure, it's harmful when you don't know the right way of using it, but when you do, you immediately can see what is a constructor function a what is a normal function. When I see this:
var p = Point();
I can't be sure if it's inherited from Point.prototype or if it's some other object. But when I see this:
var p = new Point();
I can be pretty sure it's the right object (in most cases, but I keep my code clean so I don't have to worry). So I wouldn't bury this operator, just start using it the right way and don't produce a bad code. :-)
I guess I encounter too many methods and functions that create objects to worry too much about having an operator to mark when an object is being created.
"use strict" is a good point, and I've added a note about it to the end of the article.
I'm surprised you didn't mention Douglas Crockford who's objections to "new" operator as well as the technique to avoid it are well known.
I intended to allude to it in "Prototypes are still okay" – I agree with Crockford's diagnosis, but not his prescription (though I'm not clear, his opinions may have evolved since Javascript The Good Parts). I still prefer sticking with prototypes instead of using a closure to encapsulate state, while Crockford seems to like the privacy that a closure can enforce.
(I updated the article to mention Douglas Crockford specifically.)
Use strict mode. Use JSHint. Done.
If you use a linter in your editor (like SublimeLinter for Sublime Text) then you get instant feedback if you make a mistake. Then new is no more problematic than anything else.
Reinventing new seems like overkill, especially since its been done hundreds of times (seriously, I think every new JS developer creates their own "classes" library.)
If you really want to avoid using new then, as you mention, you can polyfill Object.create. The advantage there is that it is well understood by other people reading your code and is forward compatible.