JavaScript's
prototype object generates confusion wherever it goes. Seasoned
JavaScript professionals, even authors frequently exhibit a limited
understanding of the concept. I believe a lot of the trouble stems from
our earliest encounters with prototypes, which are almost always
related to new, constructor and the very misleading prototype
property attached to functions. In fact prototype is a remarkably
simple concept. To understand it better, we just need to forget what we
'learned' about constructor prototypes and start again from first
principles.
What is a prototype?
A prototype is an object from which other objects inherit properties
Can any object be a prototype?
Yes.
Which objects have prototypes?
Every object has a prototype by default. Since prototypes are
themselves objects, every prototype has a prototype too. (There is only
one exception, the default object prototype at the top of every
prototype chain. More on prototype chains later)
OK back up, what is an object again?
An object in JavaScript is any unordered collection of key-value
pairs. If its not a primitive (undefined, null, boolean, number or
string) its an object.
You said every object has a prototype. But when I write ({}).prototype I get null. Are you crazy?
Forget everything you learned about the prototype property - it's
probably the biggest source of confusion about prototypes. The true
prototype of an object is held by the internal [[Prototype]] property. ECMA 5 introduces the standard accessor Object.getPrototypeOf(object)
which to-date is only implemented in Firefox and Chrome. In addition
all browsers except IE and Opera support the non-standard accessor __proto__. Failing that we need to ask the object's constructor for its prototype property.
04 | Object.getPrototypeOf(a); |
10 | a.constructor.prototype; |
Ok fine, but false is a primitive, so why does false.__proto__ return a value?
When a primitive is asked for it's prototype it will be coerced to an object.
2 | false .__proto__ === Boolean( false ).__proto__; |
I want to use prototypes for inheritance. What do I do now?
It rarely makes sense to set a prototype for one instance and only
one instance, since it would be equally efficient just to add
properties directly to the instance itself. I suppose if we have
created a one off object which we would like to share the functionality
of an established object, such as Array, we might do something like
this (in __proto__ supporting browsers).
3 | a.__proto__ = Array.prototype; |
But the real power of prototype is seen when multiple instances
share a common prototype. Properties of the prototype object are
defined once but inherited by all instances which reference it. The
implications for performance and maintenance are obvious and
significant.
So is this where constructors come in?
Yes. Constructors provide a convenient cross-browser mechanism for assigning a common prototype on instance creation.
Just before you give an example I need to know what this constructor.prototype property is all about?
OK. Firstly JavaScript makes no distinction between constructors and
other functions, so every function gets a prototype property.
Conversely, anything that is not a function does not have such a
property.
05 | var A = function (name) { |
So now the definition: A function's prototype property
refers to the object that will be assigned as the prototype to all
instances created when this function is used as a constructor.
It's important to understand that a function's prototype property has nothing to do with it's actual prototype.
2 | var A = function (name) { |
6 | A.prototype == A.__proto__; |
7 | A.__proto__ == Function.prototype; |
Example please?
You've probably seen and used this a hundred times but here it is once again, maybe now with added perspective.
02 | var Circle = function (radius) { |
09 | Circle.prototype.area = function () { |
10 | return Math.PI* this .radius* this .radius; |
14 | var a = new Circle(3), b = new Circle(4); |
That's great. And if I change the constructor's prototype, even existing instances will have access to the latest version right?
Well....not exactly. If I modify the existing prototype's property then this is true, because a.__proto__ is a reference to the object defined by A.prototype at the time it was created.
01 | var A = function (name) { |
05 | var a = new A( 'alpha' ); |
But if I replace the prototype property with a new object, a.__proto__ still references the original object.
01 | var A = function (name) { |
05 | var a = new A( 'alpha' ); |
What does a default prototype look like?
An object with one property, the constructor.
2 | A.prototype.constructor == A; |
What does instanceof have to do with prototype?
The expression a instanceof A will answer true if a's prototype falls within the same prototype chain as A's prototype property. This means we can trick instanceof into failing
04 | a.__proto__ == A.prototype; |
08 | a.__proto__ = Function.prototype; |
So what else can I do with prototypes?
Remember I said that every constructor has a prototype
property which it uses to assign prototypes to all instances it
generates? Well that applies to native constructors too such as
Function and String. By extending (not replacing!) this property we get
to update the prototype of every instance of the given type.
I've used this technique in numerous previous posts to demonstrate function augmentation. For example the tracer utility I introduced in my last post needed all string instances to implement times, which returns a given string duplicated a specified number of times
1 | String.prototype.times = function (count) { |
2 | return count < 1 ? '' : new Array(count + 1).join( this ); |
Tell me more about how inheritance works with prototypes. What's a prototype chain?
Since every object and every prototype (bar one) has a prototype, we
can think of a succession of objects linked together to form a
prototype chain. The end of the chain is always the default object's
prototype.
The prototypical inheritance mechanism is internal and non-explicit. When object a is asked to evaluate property foo, JavaScript walks the prototype chain (starting with object a itself), checking each link in the chain for the presence of property foo. If and when foo is found it is returned, otherwise undefined is returned.
What about assigning values?
Prototypical inheritance is not a player when property values are set. a.foo = 'bar'
will always be assigned directly to the foo property of a. To assign a
property to a prototype you need to address the prototype directly.
And that about covers it. I feel I have the upper hand on the
prototype concept but my opinion is by no means the final word. Please
feel free to tell me about errors or disagreements.
Where can I get more information on protoypes?
I recommend this excellent article by Dmitry A. Soshnikov
|