DontEnum ]DontEnum ]
]
Each object has with it an associated [[Prototype]] object
(§4.3.5,
§8.6.2).
8.6.2 Internal Properties and Methods
Native ECMAScript objects have an internal property called
[[Prototype]]. The value of this property is either null or an object and is used for implementing inheritance. Properties of the[[Prototype]]object are visible as properties of the child object for the purposes of[[Get]]access, but not for[[Put]]access.
The prototype chain (§4.2.1) is a linkage of [[Prototype]] objects
4.3.5 Prototype
A
[[Prototype]]is an object used to implement structure, state, and behaviour inheritance in ECMAScript. When a constructor creates an object, that object implicitly references the constructor's associatedprototypefor the purpose of resolving property references. The constructor's associatedprototypecan be referenced by the program expressionconstructor.prototype, and properties added to an object's prototype are shared, through inheritance, by all objects sharing the prototype.
The specification explains this pretty well in § 4.2.1, but is ambiguous in its terminology. I have changed the word prototype to [[Prototype]], where
appropriate, to avoid confusion with the term prototype, which the following paragraph from the specification uses ambiguously.
4.2.1 Objects
ECMAScript supports prototype-based inheritance. Every constructor has an associated
prototype, and every object created by that constructor has an implicit reference to the[[Prototype]]associated with its constructor. Furthermore, a[[Prototype]]may have a non-null implicit reference to its[[Prototype]], and so on; this is called the prototype chain. When a reference is made to a property in an object, that reference is to the property of that name in the first object in the prototype chain that contains a property of that name. In other words, first the object mentioned directly is examined for such a property; if that object contains the named property, that is the property to which the reference refers; if that object does not contain the named property, the[[Prototype]]for that object is examined next; and so on.
The last sentence of that quote implies that prototype chain is live; it can be dynamically modified after an object is created.
The internal [[Prototype]]
associated with each object is a property assigned to each object during
[[Construct]] (§ 13.2.2
§ 4.2.1),
[[Prototype]] holds the value of the Function object's prototype property, which in
turn holds [[Prototype]] objects.
The prototype chain is linkage of the internal
[[Prototype]]. JavaScript uses [[Prototype]] with the internal
[[Get]](P) method (§8.6.2.1) to resolve property reference identifiers.
[[Get]](P)
The the prototype chain is a linkage that allows us to create subclasses in JavaScript.
Each subclass will have the prototype
chain associated with it. Attempting to access a property of an object results in the
internal [[Get]](P) method being called. If the property is found, it is returned.
If the property is not found on the object, if
the object has a [[Prototype]] object associated with it, [[Get]](P) is called on the
[[Prototype]] object, and so on until no [[Prototype]] object is not found
(in which case undefined is returned).
8.6.2.1 [[Get]] (P)
When the
[[Get]]method of O is called with property name P, the following steps are taken:
- If O doesn't have a property with name P, go to step 4.
- Get the value of the property.
- Return
Result(2).- If the
[[Prototype]]of O is null, return undefined.- Call the
[[Get]]method of[[Prototype]]with property name P.- Return
Result(5).
This can be shown in a diagram. [[Get]] diagram
The internal [[Prototype]] object is exposed in Mozilla, Safari, and ActionScript via a
non-standard object property
__proto__. This very useful property makes the language more transparent to
the programmer. This can be shown much more easily with an example.
In Mozilla, it is possible to crawl up the DOM interface heirarchy: JavaScript-DOM Prototypes in Mozilla.
The best way to understand the prototype chain is through an example.
[[Prototype]] Object and __proto__
The prototype chain is standard, but hidden in most implementations.
__proto__ represents the internal [[Prototype]] object. __proto__ isn't standard and isn't supported in IE or Opera.
The example creates a four-class-deep inheritance hierarchy (5 if you count Object), crawling up the prototype chain and examining
the constructor property.
(function(){
// F gets a default prototype associated with it, F.prototype
// (f = new F) f gets a it's __proto__ from F in expression.
// f.__proto__ = F.prototype
// (Webkit skips this next step)
// f.__proto__.__proto__ = F.prototype.__proto__ == (an Object)
// f.__proto__.__proto__.__proto__ = o.__proto__ == Object.prototype
// f.__proto__.__proto__.__proto__.__proto__ = Object.__proto__ == Object.prototype
// Object.prototype.__proto__ == null
function F(){
this.name = "a generic f";
this.toString = function() {
return this.name;
};
}
function G(){ this.name = "Garrett Smith"; }
G.prototype = new F;
function H(){ this.name = "Hello." }
H.prototype = new G;
function I(){}
I.prototype = new H;
var result = [ ];
// Crawl up the prototype chain.
if( "__proto__" in {} ) {
for( var proto = new I; proto != null; proto = proto.__proto__ ) {
if(proto.constructor) {
var constructorName = getFunctionName(proto.constructor);
result.push(
result.length + 1 + ") " // 1)
+ constructorName + "; " // F
+ proto.toString()); // get the "name"
}
}
result.push("--------------------------");
if(F.hasOwnProperty("__proto__"))
result.push('F.hasOwnProperty("__proto__")');
if(F.__proto__ === Function.prototype)
result.push("F.__proto__: Function.prototype");
}
else {
result = ["not supported"];
}
document.getElementById("test-__proto__-result").innerHTML = (result.join("\n"));
function getFunctionName(f) {
var match = /function\s+(\w+)/.exec(f);
if(match)
return match[1];
return f.toString();
};
})();
| Result: |
|---|
| Internet Explorer | Mozilla | Opera | Safari 2 | Safari 3 |
|---|---|---|---|---|
| not supported | 1) F; Hello. 2) F; Hello. 3) F; Garrett Smith 4) F; a generic f 5) F; [object Object] 6) F; [object Object] 7) Object; [object Object] -------------------------- | not supported | 1) F; Hello. 2) F; Hello. 3) F; Garrett Smith 4) F; a generic f 5) F; [object Object] 6) [function]; [object Object] -------------------------- F.hasOwnProperty("__proto__") F.__proto__: Function.prototype | 1) F; Hello. 2) F; Hello. 3) F; Garrett Smith 4) F; a generic f 5) F; [object Object] 6) [function]; [object Object] -------------------------- F.hasOwnProperty("__proto__") F.__proto__: Function.prototype |
When new I is called, a new Object is created and used for the this argument of I.
The new object gets it's initial [[Prototype]] object from I.prototype. This
[[Prototype]] object is an internal mechanism. Gecko and Webkit expose this property as __proto__.
Object instances don't have a prototype property, but the do have a prototype chain. An object
gets its prototype chain during a call to new SomeFunction.
The only object that does not have
a prototype chain is Object.prototype. That is the end of the line. I explain this in further
detail below, in
The toString method is way up on the i
object's [[Prototype]]; all the way at the top. You can see from the diagram that it is in the object G.prototype,
which was assigned a new F. A new F has a toString method.
Before toString is called, toString must first be resolved as a property. It is resolved way up on
the prototype chain, where G.prototype is assigned a new F again.
After toString is resolved to a function, the value (a function), is invoked using (). A thisArg of the object that
toString was called on is passed in to the function call. The property
name is resolved on the object; this.name resolves first to I.prototype.name
(I.prototype.name is assigned a new H).
Safari does not have F.prototype.__proto__ assigned to an object (step 5). Although the
__proto__ property is nonstandard, Safari's behavior contradicts
15.3.5.2, Properties of Function Instances:
A
This is
further evidenced by the fact that Safari does not have prototype property is automatically created for every function, to provide
for the possibility that the function will be used as a constructor.Function.prototype.prototype
.
It's in the new object's [[Prototype]]. Specifically, on the prototype of the function
that created the object. Each Function object gets a prototype automatically created
for it. I'll explain this next.
Unfortunately, the constructor property is resolved to F. Function G's default, given constructor property was on G.prototype but it went away when we assinged G.prototype = new F. The constructor property can now only be resolved up the prototype chain, where it is found on F.prototype, having the value F. When we assign H.prototype = new G. The
constructor property can only be
resolved to F, and so on. For each subclass, the constructor property points to F.
This is a problem.
The constructor being automatically given on the Function's prototype (and not the object itself) is counterintuitive
and not very useful. It is a problem with the language that needs to be changed.
I'll explain a way to partially mend JavaScript's broken
constructor property soon.
prototype
A prototype property is automatically created for every function
(§15.3.2.1).
This property does not have any attributes
§15.3.5.2).
A Function's given prototype is an Object object, but is specialized in that it has
its own constructor property. The constructor property has the attributes {DontEnum}.
Do not confuse a Function's prototype with [[Prototype]]. Don't confuse either
with Function.prototype, the prototype property that all function objects have in their
prototype chain.
A Function's prototype does not have the DontEnum Attribute.
constructor on the prototype?
The constructor property is added to the function's prototype
with the attributes {DontEnum}. This happens when a function is created. I cannot say
why the language was designed this way. I think it is a mistake.
Lets see what kinds of problems this can create.
A common idiom is to create a prototype for a function as an object literal.
Question #1: Look at the code below and see if you can get the correct answer before clicking the button.
(function(){
function F(){}
F.prototype = {
};
var f = new F();
return [
"f.constructor === F: " + (f.constructor === F),
"f.constructor === Object: " + (f.constructor === Object)
].join(String.nl);
})();
Can you guess what the answer will be?
Question #2: Does this result make any logical sense?
f.constructor === F: false f.constructor === Object: true
We have lost F.prototype.constructor. This is not surprising if you understand the code. This odd behavior is correct, according to the specification. All the browsers concur.
To fix the missing constructor problem, it seems like we can add it back manually.
(function(){
function F(){}
F.prototype = {
constructor : F
};
return [
isPropertyEnumerable(F, "prototype"), //true;
isPropertyEnumerable(F.prototype, "constructor") //true;
].join(" ");
})();
| Result | Expected |
|---|---|
| true true |
| Internet Explorer | Mozilla | Opera | Safari 2 | Safari 3 |
|---|---|---|---|---|
| false false | true true | false true | true true | true true |
The constructor property is now enumerable, as it is a custom property on a programmer-defined object. The object is not a prototype object created by §13.2, Creating Function Objects. Enumerability has been modified.
When an Object object is created by new Object, none of the properties
that a programmer gives the object can or will have the DontEnum attribute.
Contrast to that: When an Object object is created by new Object, if that object was created by the script engine
(not the programmer) in Step 9. of §13.2, Creating Function Objects,
then the object will have a constructor property given with the DontEnum attribute.
Are you confused yet?
An object's constructor property may or may not be enumerable. Internet explorer always treats a
constructor property as if it had the DontEnum attribute.
A Function's Default prototype.constructor
Should be Object
In conjunction with the recommendation to
Add the constructor property to the new object in [[Construct]],
I recommend that ECMAScript 4 change the algorithm for §13.2, Creating Function Objects, step 10.
Δ 10. Set the constructor property of Result(9) to Object
F.
This property is given attributes {DontEnum}.
This recommendation follows the recommendation to
Add the constructor property to the new object in [[Construct]].
It is redundant to state that an Object object's constructor should be Object, however, ES3 sets
prototype.constructor to the value of the function it was automatically created for.
Add constructor Property to the New Object in [[Construct]]
§13.2.2
This property is given the attributes {DontEnum, DontDelete}.
I have struck out and highlighted my proposed changes and additions
to the [[Construct]] algorithm of §13.2.2.
[[Construct]]
When the [[Construct]] property for a Function object F is called, the following steps are taken:
[[Class]] property of Result(1) to prototype property of F.
constructor property of Result(1) to F with the attributes {DontDelete, DontEnum}
Result(3) is an object, set the [[Prototype]] property of Result(1) to Result(3).
Result(3) is not an object, set the [[Prototype]] property of Result(1) to the original Object prototype object as described in §15.2.3.1.
[[Call]] property of F, providing Result(1) as the this value and providing the argument list passed into [[Construct]] as the argument values.
Type(Result(6)) is Object then return Result(6).
Δ Note: Although allowed in ES3, Functions used as constructors should not return a value.
Δ Note: Although the constructor is read-write in ES4, Future versions of ECMAScript may give the constructor
the {ReadOnly} attribute.
Next in this tutorial: Create a Subclass