views:

77

answers:

3

Hi There,

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!?

A: 

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.

lawnsea
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.
CMS
Thanks for the response. Do you know of any decent articles on shallow and deep copy?
Richard
@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 Matteis
@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](http://jsbin.com/uqope/edit)
CMS
@CMS - but mutating obj2 doesn't affect obj1 which surely means single properties / methods are copied? Mutating obj1 affects obj2 because, as you say, obj1 is obj2's pototype but not vice versa.
Richard
@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.
CMS
@CMS: Doh! You're right, the only thing copied is the reference to `o`.@Richard, Sorry for muddying the waters with this talk of shallow/deep copying. See Crockford's Advanced Javascript videos for a great explanation of the prototype chaining that CMS is talking about: [Part 1](http://video.yahoo.com/watch/111585/1027823 "Crockford's Advanced Javascript Part 1"), [Part 2](http://video.yahoo.com/watch/111586/1027832 "Crockford's Advanced Javascript Part 2"), [Part 3](http://video.yahoo.com/video/play?vid=111587 "Crockford's Advanced Javascript Part 3")
lawnsea
+3  A: 

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'
};
CMS
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'.
Richard
A: 

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 Matteis