So far we’ve learned what the relationship is between an object and its constructor’s prototype
and what happens when we change properties set on the prototype
. In particular, we learned that if you try to access a property of on object, the JavaScript engine will first look in the object itself for the property, and it if doesn’t find it there it looks in its __proto__
property, which is also the constructor’s prototype
.
This leads to a good question: What happens if the property isn’t in the constructor’s prototype either? One possible answer is that the JavaScript engine gives up and says the property is undefined
. But that’s not what happens.
Object
To understand what does happen, we need to understand the prototype chain. And to better understand that, let’s start with the root of all JavaScript objects: the Object
constructor function. Being a function, it has a prototype
property attached to it.
console.log(Object.prototype);
If you look through Object.prototype
‘s properties, you’ll see a host of methods for doing things like converting the object to a string and so on.
Object.prototype {…} __defineGetter__: function __defineGetter__() __defineSetter__: function __defineSetter__() __lookupGetter__: function __lookupGetter__() __lookupSetter__: function __lookupSetter__() __proto__: Getter & Setter constructor: function Object() hasOwnProperty: function hasOwnProperty() isPrototypeOf: function isPrototypeOf() propertyIsEnumerable: function propertyIsEnumerable() toLocaleString: function toLocaleString() toSource: function toSource() toString: function toString() valueOf: function valueOf()
When you create an object literal or instantiate an Object with new
, that new object’s __proto__
will reference Object.prototype
:
foo = {}; bar = new Object(); console.log( foo.__proto__ === Object.prototype ); // true console.log( bar.__proto__ === Object.prototype ); // true
Given what we know, if we try to access a property that’s not in the object itself:
console.log( foo.toString() );
we expect that the JavaScript engine will first look in the object (foo
) and if it’s not there, it will look in the object’s __proto__
, which in this case is Object.prototype
.
Since Object.prototype
is itself an object, you might expect it to have a __proto__
property. It does:
console.log( Object.prototype.hasOwnProperty('__proto__') ); // true
but it’s value is null
(not undefined!):
console.log( Object.prototype.__proto__ ); // null
Thus foo
has a __proto__
, and that _proto_
has a _proto_
as well:
console.log(foo.__proto__); // same as Object.prototype console.log(foo.__proto__.__proto__); // same as Object.prototype.__proto__
The prototype chain
Even though this isn’t a particularly long chain of __proto__
s, it shows that there is a prototype chain associated with every JavaScript object.
Let’s take a deeper dive into prototype chains by looking at the chain that results when you instantiate an object with a constructor you’re written.
function Person(name) { this.name = name; } Person.prototype.greeting = 'Hello. My name is'; Person.prototype.sayName = function() { console.log(this.greeting, this.name); }; var fuzz = new Person('Fuzz');
We expect that fuzz
‘s __proto__
will be Person.prototype
:
console.log( fuzz.__proto__ === Person.prototype ) // true
We also know that the constructor’s prototype
is an object. Being an object, we might ask whether it also has a __proto__
property.
console.log( Person.prototype.__proto__ ); // same as console.log( fuzz.__proto__.__proto__ );
Indeed it does. Person.prototype
is an object — one that was constructed with the Object
function. (Functions’ prototypes are constructed from Object()
by default). So, we might expect that Person.prototype.proto refers to its constructor’s prototype
, that is, Object.prototype
. It does:
console.log( Person.prototype.__proto__ === Object.prototype ); // true, same as console.log( fuzz.__proto__.__proto__ === Object.prototype ); // true
In the last statement above, we can see the prototype chain starts with an object’s __proto__
and ends not with Object.prototype
but actually with Object.prototype.__proto__
(which is null
).
console.log( Object.prototype.__proto__ ); // null (not undefined)
Let’s now have another look at:
console.log( fuzz.toString() );
The toString
property isn’t actually defined in the Person constructor or its prototype
. It’s actually defined in Object
‘s prototype
:
console.log( Object.prototype.hasOwnProperty('toString') ); // true
But clearly fuzz
has no issues accessing toString
. So here’s the full process: The JavaScript engine first looks in the object itself for a property. If it doesn’t find it there it looks in its __proto__
property. If it doesn’t find it there, it looks in __proto__
‘s __proto__
… and up the chain until it finds what it’s looking for or __proto__
is null
. If it goes so far up the chain that __proto__
is null
, then the property is undefined
.
This is the prototype chain in action.
Constructed versus factory generated objects
One last thing we should cover before wrapping this installment up is a subtle difference between objects you might make with constructor functions and those you might make with factory functions.
We have established that the __proto__
of an object instantiated with a constructor function is the prototype
of the constructor. That’s just something JavaScript does automatically. However, when you create an object with a factory function using the pattern below:
function makePerson(name) { return { name: name, greeting: 'Hello. My name is', sayName: function () { console.log(this.greeting, this.name); } }; } var fuzz = makePerson('Fuzz');
you are creating an object literal and returning a reference to that object. As we established above, the __proto__
of an object literal is Object.prototype
. Thus fuzz.__proto__
in this example is Object.prototype
, not makePerson.prototype
.
This is a subtle difference that could land you in trouble if you forget.
In JavaScript, you should Never, ever mess around with Object
. The chances that your tweaks and additions might accidentally clobber something now or in the future in that hugely critical function is too great. By extension this includes everything in Object.prototype
.
In the example above, if you absentmindedly decided you wanted to stuff something common to Persons made with your factory function in the objects’ __proto__
:
fuzz.__proto__.sayNameUnfriendly = function () { console.log("What's it to you?"); };
you will actually be dropping it into Object.prototype
. This is bad bad bad! And it’s one reason to prefer constructor functions over simple factory functions.