13

I've come across a peculiarity with Douglas Crockfords Object.create method which I'm hoping someone might be able to explain:

If I create an object - say 'person' - using object literal notation then use Object.create to create a new object - say 'anotherPerson' - which inherits the methods and properties from the initial 'person' object.

If I then change the name values of the second object - 'anotherPerson' - it also changes the name value of the initial 'person' object.

This only happens when the properties are nested, this code should give you an idea of what I mean:

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
};

// initiate new 'person' object
var person = {
    name: {
        first: 'Ricky',
        last: 'Gervais'
    },
    talk: function() {
        console.log('my name is ' + this.name.first + ' ' + this.name.last);
    }
}

// create anotherPerson from person.prototype
var anotherPerson = Object.create(person);
// change name of anotherPerson
anotherPerson.name.first = 'Stephen';
anotherPerson.name.last = 'Merchant';

// call talk method of both 'person' and 'anotherPerson' objects
person.talk(); // oddly enough, prints 'Stephen Merchant'
anotherPerson.talk(); // prints 'Stephen Merchant'

If I were to store the name values without nesting then this odd behaviour does not occur -- e.g.

// initiate new 'person' object
var person = {
    firstName: 'Ricky',
    lastName: 'Gervais',
    talk: function() {
        console.log('my name is ' + this.firstName + ' ' + this.lastName);
    }
}

// create anotherPerson from person.prototype
var anotherPerson = Object.create(person);
// change name of anotherPerson
anotherPerson.firstName = 'Stephen';
anotherPerson.lastName = 'Merchant';

// call talk method of both 'person' and 'anotherPerson' objects
person.talk(); // prints 'Ricky Gervais'
anotherPerson.talk(); // prints 'Stephen Merchant'

This nesting issue doesn't seem to occur when using a classical style of inheritance with a constructor function and the 'new' keyword.

I'd be much appreciative if anyone's able to explain why this occurs!?

1

3 Answers 3

20

That happens because anotherPerson.name is an object and it is stored upper in the prototype chain, on the original person object:

//...
var anotherPerson = Object.create(person);
anotherPerson.hasOwnProperty('name'); // false, the name is inherited
person.name === anotherPerson.name; // true, the same object reference

You can avoid this by assigning a new object to the name property of the newly created object:

// create anotherPerson from person
var anotherPerson = Object.create(person);

anotherPerson.name = {
  first: 'Stephen',
  last: 'Merchant'
};
Sign up to request clarification or add additional context in comments.

4 Comments

Wow, Stack overflow is more like instant messenger than a forum! Thanks for the response, that makes sense -- so the second object only has the name object by reference, it's not actually copied. I then assume the original 'person' object would evaluate hasOwnProperty('name') === true as it is the prototype of 'anotherPerson'.
I'm new to Javascript, but I'm guessing you could also use: 'anotherPerson.name = Object.create(person.name);' to have the new nested object inherit from the old nested object if desired.
This is already an old answer but I still have an question for this. I understand B.name doesn't exist and it goes up to find A.name, thus it replaces A.name's value. However, why in the 2nd example, the "B.firstName/lastName" exist in B object and doesn't override A's properties?
NVM. I figured out myself. So 'name' is just a pointer however 'firstName' and 'lastName' is setter. Thus 'firstName and lastName' is assigned to the B object instead of going up to find it in A prototype.
2

The problem is that Object.create only does a shallow copy, not a deep copy, so person.name and anotherPerson.name both point to the same Object instance.

Edited

While it's true that person.name === anotherPerson.name, my explanation for why this is true is incorrect. See @CMS's answer for the correct explanation.

7 Comments

True, both point to the same instance, but actually, Object.create doesn't makes an object copy at all, it just creates a new empty object that inherits from the original.
Thanks for the response. Do you know of any decent articles on shallow and deep copy?
@CMS, Actually F.prototype = o; is a copy. It copies the object and its properties inside the prototype of the new object... and the reason the name attribute isn't copied is because object literals in JavaScript are always references, therefore the reference is copied (not its content) ... so it is not because it is deeper in the prototype chain or because it's doing a shallow copy.
@Luca: F.prototype = o; only copies the reference of the object, the properties aren't copied. When a property lookup is made and if it doesn't exist physically on the object, the check continues up through the prototype chain, the name property on the anotherPerson object is not found as own property there (anotherPerson.hasOwnProperty('name') returns false), then the property is reached on the person object, which is the [[Prototype]] of anotherPerson. Mutating that object affects both objects, but again no copy is made. Check this example
@Richard, when you make an assignment an object property reference, if the property doesn't exist, it will be created physically on the object, as an own property, and it will shadow any property with the same name higher in the prototype chain. If the property already exist on the object, its value is simply changed. Basically, the differential inheritance takes place when a property is looked up, when you make an assignment to an object property, the prototype chain is not affected, only the base object where the property is being added/changed.
|
1

The reason the name attribute isn't copied is because object literals in JavaScript are always references, therefore the reference is copied (not its content) ... so it is not because it is deeper in the prototype chain or because it's doing a shallow copy.

1 Comment

This makes perfect sense to me.. is it still correct?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.