javascript - Object.create explanation
-
30-06-2021 - |
Question
I have a question about the following canonical object.create method:
Object.create = function(o, props) {
function F() {}
F.prototype = o;
if (typeof(props) === "object") {
for (prop in props) {
if (props.hasOwnProperty((prop))) {
F[prop] = props[prop];
}
}
}
return new F();
};
On line 3 of the above code we set the prototype property of the F object to the o argument's prototype.
I would have thought this meant that both o and F point to the same prototype and therefore point to the same set of members.
But the code then goes onto copy all the members in the prop in props loop.
What is the point of setting the prototype in line 3 if we then go onto copy all the members manually?
Solution
There's a mistake in the version of Object.create
in your question: The loop attaches the properties to the constructor function F
(not to the returned object, or its prototype) which means they're not accessible in the created object.
The properties of the second parameter to Object.create
are supposed to be copied to the newly created object. The Mozilla documentation for Object.create
puts it like this:
If specified and not undefined, an object whose enumerable own properties (that is, those properties defined upon itself and not enumerable properties along its prototype chain) specify property descriptors to be added to the newly-created object, with the corresponding property names.
Try running the following code with the version of Object.create
in the question:
o = Object.create(
{a: "prototype's a", b: "prototype's b"},
{a: "object's a"}
);
You'll find that o.a == "prototype's a"
and o.b == "prototype's b"
, "object's a"
is lost.
The following version of the function would probably be more useful:
Object.create = function(o, props) {
var newObj;
// Create a constructor function, using o as the prototype
function F() {}
F.prototype = o;
// Create a new object using F as the constructor function
newObj = new F();
// Attach the properties of props to the new object
if (typeof(props) === "object") {
for (prop in props) {
if (props.hasOwnProperty((prop))) {
newObj[prop] = props[prop];
}
}
}
return newObj;
};
Let's try it out with the same example:
o = Object.create(
{a: "prototype's a", b: "prototype's b"},
{a: "object's a"}
);
The new object o
is created with a prototype that has properties a
and b
and it's own property a
.
Let's look at o.b
first: o.hasOwnProperty("b")
will return false
, because o
does not have a property called b
. That's where the prototype comes in; because there is no property b
it is looked up on the prototype, and therefore o.b === "prototype's b"
.
On the other hand, o.hasOwnProperty("a")
will return true
, because o
does have an a
property. o.a == "object's a"
and nothing is looked up from the prototype.
As pointed out in @chuckj's answer, the correct implementation of Object.create
is more complicated than this. For more details, see John Resig's post on ECMAScript 5 Objects and Properties.
OTHER TIPS
The code you present is not equivalent to Object.create()
as defined by the ECMA standard. The members of the second parameter, here called prop
, is supposed to be a set of descriptors to be defined on the resulting object, not copied to the constructor function. This is more accurate (but not exacly right either),
Object.create = function(o, props) {
function F() {}
F.prototype = o;
var result = new F();
if (typeof(props) === "object")
for (prop in props)
if (props.hasOwnProperty(prop) && typeof(props[prop].value) != "undefined")
result[prop] = props[prop].value;
return result;
};
The props
parameter contains the property descriptors for the members of the new object you want to be different than specified in the prototype, o
. The values are not supposed to be copied literally into the object rather they are supposed to be processed as if you called Object.defineProperties()
, above simulated by assigning the value
part of the descriptor to the new object. Object.create()
cannot be accurately polyfilled to ECMA 3 since it can be used to specifiy properties with accessor functions, though the above will work for props
which use value
instead of get
or set
.
If you are not interested in a polyfill version, the following is an accurate description of what Object.create()
does, assuming the existence of both __proto__
and Object.defineProperties()
.
Object.create = function (o, props) {
if (typeof(o) !== "object" && o != null) throw new TypeError();
var result = new Object();
if (o != null)
result.__proto__ = o;
if (typeof(props) !== "undefined")
Object.defineProperties(result, props);
return result;
};
Think of F
as a function that returns a new instance of an object and F.prototype
a reference to the object that the new instance is borrowing properties from.
Instances created with F
have their own property set independant of F.prototype
.
When a property cannot be found in an incance of F
then the runtime will step up the prototype chain and peek to see if the property exists in F.prototype
.
This is why you would want to copy some and inherit others.
The point of the Prototype is to provide class-like code reuse/property storage.
Think of F.prototype
as the class instances of F
as the object.