Skip to content

Instantly share code, notes, and snippets.

@rlemon
Forked from ryankinal/illusion-of-class.html
Last active December 11, 2015 21:58
Show Gist options
  • Save rlemon/4665933 to your computer and use it in GitHub Desktop.
Save rlemon/4665933 to your computer and use it in GitHub Desktop.
This indirection was intended to make the language seem more familiar to classically trained programmers, but failed to do that, as we can see from the very low opinion Java programmers have of JavaScript. JavaScript's constructor pattern did not appeal to the classical crowd. It also obscured JavaScript's true prototypal nature. As a result, there are very few programmers who know how to use the language effectively.

~ Douglas Crockford, "Prototypal Inheritance"

The indirection Mr. Crockford refers to here is the Pseudo-Classical Pattern, also known as the Constructor Pattern. This is a way of simulating classical inheritance in JavaScript - a prototypal language.

If it wasn't obvious from both the title of this post, and from the frankly negative quote above, this is not my favorite object-oriented pattern in JavaScript. I'll go into this in further detail as I teach the pattern, but the reasons have to do with semantics, usability, verbosity, and blatant indirection. If you don't pay too much attention to the details, the pattern is actually rather usable. But when you get into the meat of how this pattern simulates classical inheritance from a prototypal system, you see the flaws in the foundation. That said, the pattern can and has been used effectively, and there is one distinct advantage it has over other OO patterns in JS. We'll get to that later as well.

I'll be examining the pseudo-classical pattern in comparison with the basic prototypal system. If you don't know how JavaScript's prototype system works, then I suggest you read Objects and the Prototype Chain, and come back later.

Otherwise, let's jump right in.

The Basics

[javascript] var Point = function(x, y) { this.x = x; this.y = y; }

Point.prototype.translate = function(x, y) { this.x += x; this.y += y; }

var point = new Point(17, 42); point.translate(5, 6); [/javascript]

Easy enough! But what do we end up with? Well, we end up with a new object of type Point with a prototype equal to Point.prototype. Check it out.

[javascript] Point x: 22 y: 48 proto: Object constructor: function (x, y) translate: function (x, y) proto: Object [/javascript]

We have a Point object, with x and y properties, and a prototype that contains the translate function. Sweet! Now for something a little more complicated.

Inheritance

Unlike a normal classical language, JavaScript has no extends keyword. There is no way for a constructor to directly inherit from another constructor.

Well... sort of. There's the prototype property that functions have. And when the function is used as a constructor (with the new keyword), you get a new object that has a prototype equal to the function's prototype property, so a new "instance" of a constructor function will inherit from that function's prototype property. This is kind of like having an extends keyword.

That last paragraph was probably confusing. And I apologize for that. It really stems from the poorly-chosen naming conventions involved in the pseudo-classical pattern. I'll discuss this more later, but for now, here's an example of inheritance in the pseudo-classical pattern.

[javascript] var Point3d = function(x, y, z) { Point.call(this, x, y); this.z = z; }

Point3d.prototype = new Point();

Point3d.prototype.translate = function(x, y, z) { Point.prototype.translate.call(this, x, y); this.z += z; }

var point3d = new Point3d(19, 20, 21); point3d.translate(4, 5, 6); [/javascript]

There's a lot going on here.

First, we need to create our constructor for Point3d. Easy enough; It's a function, just like Point was in the previous example. We use this just like we did previously, but to avoid code duplication, we can use Point.call - changing the calling context of the Point function to whichever new object we create (referenced by this). The new object, in this way, gets xy, and z properties, just like we want.

Then we determine the inheritance structure by setting Point3d.prototype to a new instance of the Point constructor. All our new Point3d objects will now have a prototype equal to an object with an prototype equal to Point3d.prototype. Did you catch that? There were a lot of different "prototype"s in there, and not all of them mean the same thing.

Lastly, we take advantage of the prototype chain, setting Point3d.prototype.translate to a 3d version of the translate function. In this function, again, we take advantage of the call function to avoid code duplication.

That's a lot of structure, and the naming gets really confusing at this point. Maybe just looking at the structure will help. This is what our new Point3d object looks like:

[javascript] Point3d x: 23 y: 25 z: 27 proto: Point translate: function (x, y, z) x: undefined y: undefined proto: Object constructor: function (x, y) translate: function (x, y) proto: Object [/javascript]

I hope that clears things up a little.

Why Is This So Confusing?

Well, as mentioned at the beginning of this article, constructors were tacked on to the language at the last minute, in hopes of satisfying programmers trained in classical languages (like Java or C++). I can't imagine much thought was put into it.

This is evident in the naming conventions. Every function has a prototype property, which is used solely for the pseudo-classical style. But it is not the prototype of the function - that would be the proto property (in most JS engines). This indirection causes one really big issue, which was demonstrated above: It makes it really hard to talk about the prototype property versus the prototype of an object. The domains of the two concepts overlap, and when using the pseudo-classical pattern, they are intertwined; The use of functions as constructors depends on the prototype property, and affects the prototype of objects.

This is a big reason why I avoid the pseudo-classical pattern. It removes the prototype property from the equation, and thus reduces the cognitive load required. Ironically, if you understand how it all works, you can forget the details and might be able to take advantage of new as a useful abstraction.

What's Good?

As much as I dislike this pattern, there are some interesting, and possibly useful parts of it. This, of course, is up for debate, and different people will likely see these in completely different lights.

Familiarity

Many of us "grew up" with classical languages. "Desktop" or "Application" developers may be familiar with Java, C++, or Python, as might developers who studied Computer Science formally. Web developers are likely familiar with PHP or C#. All of these are classical languages, and it can be a boon to see familiar constructs, even if they don't function quite as they would in the languages we're used to.

It's Used Internally

When we want a new Date object, we use new Date. We can also do the same with Array if we choose (though literals are preferred). Using constructors in our own code can keep everything looking the same.

Types

With the pseudo-classical pattern, we can take advantage of instanceof and typeof operators on our own objects. If you're into that kind of thing (which you probably shouldn't be - see the What's Bad? section, below), then typeof point3d will return "Point3d" and point3d instanceof Point will be true.

The Naming Convention

This may be due to my classical education, but I really like capitalizing constructors (Point3d) and lowercasing instances (point3d). It makes naming less of a hassle.

What's Bad?

Prototype vs. Prototype

This was discussed above. The prototype property of a function is not the function's prototype, but it could be the prototype of a different object. 'nuf said.

Familiarity

The use of new might be familiar, but it's kind of familiar in that Uncanny Valley sort of way. It looks like it's classical, but it just isn't. The differences are big enough to cause issues. There is no explicit extends, and there are no real classes (there probably will be, but that's an issue for a different post). You have to fake it, which is why this is the pseudo-classical pattern.

Verbosity

There's a lot of x.prototype.y, and even some x.prototype.y.call, and that's rough on the eyes.

Forgetting new

Constructors are just functions, which means they can be called regularly, without the use of the new keyword. This causes a problem when using this inside of the constructor - it will no longer reference the new object. Instead, it will reference the global object. In the browser, this means setting this.x will set window.x. If you're diligent, this won't be a problem. But it could be an interesting bug to find.

Type

In the realm of the browser, there has long been a focus on feature detection, rather than browser detection. It's considered good practice to check for a feature before using it, rather than make assumptions based on possibly falsified data - the UA string.

The same can be said of checking for type in JavaScript. When using instanceof and typeof with native objects, you'll run into issues, so it may be a poor decision to get into the habit of using those operators with your own constructs.

It's far preferable to use Duck Typing, rather than rely on the type of an object. Check for properties, and if they exist, then use them. After all, if it "walks like a duck and swims like a duck and quacks like a duck", then it might as well be a duck.

But it's still not going to tell you it's a duck.

Un-needed Abstraction

The new keyword is an abstraction of two different steps that can be achieve quite easily using a clearer and more prototypal method. Assuming we have our structure already set up, the following pseudo-classical code:

[javascript] var point3d = new Point3d(19, 20, 21); [/javascript]

Is equivalent to the following code using the initializer pattern:

[javascript] var specificPoint = Object.create(Point3d); point3d.init(19, 20, 21); [/javascript]

Is the abstraction really needed? I guess that's up to you.

Final Thoughts

Crockford was right. The pseudo-classical/constructor pattern is an indirection. It's confusing, and it prevents people from learning the language effectively.

Underneath, it uses the prototype chain, and it adds some interesting (possibly useful) features on top of it. But if you don't understand the prototype chain to begin with, then you're probably not going to be able to use constructors effectively.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment