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?

Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top