JS101: proto
by
at 2012-11-26 08:00:00
original http://feedproxy.google.com/~r/dailyjs/~3/iGf8-YbXT3s/js101-proto
When I originally wrote about prototypes in JS101: Prototypes a few people were confused that I didn’t mention the __proto__
property. One reason I didn’t mention it is I was sticking to standard ECMAScript for the most part, using the Annotated ECMAScript 5.1 site as a reference. It’s actually hard to talk about prototypes without referring to __proto__
, though, because it serves a very specific and useful purpose.
Recall that objects are created using constructors:
function User() {
}
var user = new User();
The prototype
property can be used to add properties to instances of User
:
function User() {
}
User.prototype.greet = function() {
return 'hello';
};
var user = new User();
user.greet();
So far so good. The original constructor can be referenced using the constructor
property on an instance:
assert.equal(user.constructor, User);
However, user.prototype
is not the same as User.prototype
. What if we wanted to get hold of the original prototype where the greet
method was defined based on an instance of a User
?
That’s where __proto__
comes in. Given that fact, we now know the following two statements to be true:
assert.equal(user.constructor, User);
assert.equal(user.__proto__, User.prototype);
Unfortunately, __proto__
doesn’t appear in ECMAScript 5 – so where does it come from? As noted by the documentation on MDN it’s a non-standard property. Or is it? It’s included in Ecma-262 Edition 6, which means whether it’s standard or not depends on the version of ECMAScript that you’re using.
It follows that an instance’s constructor should contain a reference to the constructor’s prototype. If this is true, then we can test it using these assertions:
assert.equal(user.constructor.prototype, User.prototype);
assert.equal(user.constructor.prototype, user.__proto__);
The standards also define Object.getPrototypeOf – this returns the internal property of an object. That means we can use it to access the constructor’s prototype:
assert.equal(Object.getPrototypeOf(user), User.prototype);
Putting all of this together gives this script which will pass in Node and Chrome (given a suitable assertion library):
var assert = require('assert');
function User() {
}
var user = new User();
assert.equal(user.__proto__, User.prototype);
assert.equal(user.constructor, User);
assert.equal(user.constructor.prototype, User.prototype);
assert.equal(user.constructor.prototype, user.__proto__);
assert.equal(Object.getPrototypeOf(user), User.prototype);
Internal Prototype
The confusion around __proto__
arises because of the term internal prototype:
All objects have an internal property called [[Prototype]]. The value of this property is either
null
or an object and is used for implementing inheritance.
Internally there has to be a way to access the constructor’s prototype to correctly implement inheritance – whether or not this is available to us is another matter. Why is accessing it useful to us? In the wild you’ll occasionally see people setting an object’s __proto__
property to make objects look like they inherit from another object. This used to be the case in Node’s assertion module, but Node’s util.inherits
method is a more idiomatic way to do it:
// Compare to: assert.AssertionError.__proto__ = Error.prototype;
util.inherits(assert.AssertionError, Error);
This was changed in assert: remove unnecessary use of __proto__.
The Constructor’s Prototype
The User
example’s internal prototype is set to Function.prototype
:
assert.equal(User.__proto__, Function.prototype);
If you’re about to put on your hat, pick up your briefcase, and walk right out the door: hold on a minute. You’re coming to the end of the chain – the prototype chain that is:
assert.equal(User.__proto__, Function.prototype);
assert.equal(Function.prototype.__proto__, Object.prototype);
assert.equal(Object.prototype.__proto__, null);
Remember that the __proto__
property is the internal prototype – this is how JavaScript’s inheritance chain is implemented. Every User
inherits from Function.prototype
which in turn inherits from Object.prototype
, and Object.prototype
’s internal prototype is null
which allows the inheritance algorithm to know it has reached the end of the chain.
Therefore, adding a method to Object.prototype
will make it available to every object. Properties of the Object Prototype Object include toString
, valueOf
, and hasOwnProperty
. That means instances of the User
constructor in the previous example will have these methods.
Pithy Closing Remark
JavaScript’s inheritance model is not class-based. Joost Diepenmaat’s post, Constructors considered mildly confusing, summarises this as follows:
In a class-based object system, typically classes inherit from each other, and objects are instances of those classes. … constructors do nothing like this: in fact constructors have their own [[Prototype]] chain completely separate from the [[Prototype]] chain of objects they initialize.
Rather than visualising JavaScript objects as “classes”, try to think in terms of two parallel lines of prototype chains: one for constructors, and one for initialised objects.
References
- Node’s source
- Annotated ECMAScript 5.1
- Ecma-262 Edition 6
- Mozilla’s proto documentation
- Constructors considered mildly confusing