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

hippietrail
  • 15,848
  • 18
  • 99
  • 158
Richard
  • 383
  • 3
  • 11
  • 2
    possible duplicate: [Crockford's Prototypal inheritance - Issues with nested objects](http://stackoverflow.com/q/10131052/1048572) – Bergi Jun 08 '14 at 12:59

3 Answers3

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'
};
Christian C. Salvadó
  • 807,428
  • 183
  • 922
  • 838
  • 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 Jul 07 '10 at 00:41
  • 1
    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. – Sven Viking Sep 25 '13 at 09:51
  • 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? – user2734550 May 06 '16 at 23:11
  • 1
    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. – user2734550 May 06 '16 at 23:37
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.

lawnsea
  • 6,463
  • 1
  • 24
  • 19
  • 2
    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. – Christian C. Salvadó Jul 07 '10 at 00:39
  • Thanks for the response. Do you know of any decent articles on shallow and deep copy? – Richard Jul 07 '10 at 00:43
  • @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 Jul 07 '10 at 00:43
  • 2
    @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) – Christian C. Salvadó Jul 07 '10 at 04:48
  • @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 Jul 07 '10 at 07:47
  • 2
    @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. – Christian C. Salvadó Jul 07 '10 at 07:58
  • @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 Jul 07 '10 at 13:43
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.

Luca Matteis
  • 29,161
  • 19
  • 114
  • 169