Crockford's Prototypal inheritance - Issues with nested objects

douglas crockford net worth
douglas crockford books
douglas crockford twitter
douglas crockford javascript: the good parts
douglas crockford how javascript works
http javascript crockford com prototypal html
how javascript works crockford
crooked crockford

I've been reading "Javascript: The Good Parts" by Douglas Crockford - and while it's a bit extreme, I'm on board with a lot of what he has to say.

In chapter 3, he discusses objects and at one point lays out a pattern (also found here) for simplifying & avoiding some of the confusion/issues that come with the use of the built-in "new" keyword.

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

So I've tried using this in a project I'm working on, and I noticed an issue when attempting to inherit from objects that are nested. If I overwrite a value of a nested object inherited using this pattern, it overwrites the nested element all the way up the prototype chain.

Crockford's example is like the flatObj in the following example, which works well. The behaviour, however, is inconsistent with nested objects:

var flatObj = {
    firstname: "John",
    lastname: "Doe",
    age: 23
}
var person1 = Object.create(flatObj);

var nestObj = {
    sex: "female",
    info: {
        firstname: "Jane",
        lastname: "Dough",
        age: 32  
    }
}
var person2 = Object.create(nestObj);

var nestObj2 = {
    sex: "male",
    info: {
        firstname: "Arnold",
        lastname: "Schwarzenneger",
        age: 61  
    }
}
var person3 = {
    sex: "male"
}
person3.info = Object.create(nestObj2.info);

// now change the objects:
person1.age = 69;
person2.info.age = 96;
person3.info.age = 0;

// prototypes should not have changed:
flatObj.age // 23
nestObj.info.age // 96 ???
nestObj2.info.age // 61

// now delete properties:
delete person1.age;
delete person2.info.age;
delete person3.info.age;

// prototypes should not have changed:
flatObj.age // 23
nestObj.info.age // undefined ???
nestObj2.info.age // 61

(also on a fiddle)

Am I doing something wrong, or is this a limitation of this pattern?


There is no inconsistency. Just don't think of nested objects: a direct property of an object is always either on its prototype or an own property. It's irrelevant wheter the property value a primitive or an object.

So, when you do

var parent = {
    x: {a:0}
};
var child = Object.create(parent);

child.x will be referencing the same object as parent.x - that one {a:0} object. And when you change a property of it:

var prop_val = child.x; // == parent.x
prop_val.a = 1;

both will be affected. To change a "nested" property independently, you first will have to create an independent object:

child.x = {a:0};
child.x.a = 1;
parent.x.a; // still 0

What you can do is

child.x = Object.create(parent.x);
child.x.a = 1;
delete child.x.a; // (child.x).a == 0, because child.x inherits from parent.x
delete child.x; // (child).x.a == 0, because child inherits from parent

which means they are not absolutely independent - but still two different objects.

Douglas Crockford's JavaScript, JavaScript. Actual JavaScript Engine Performance · JavaScript: The Wrrrld's Most Misunderstood Programming Language · The World's Most Misunderstood  2020-03-16 It Was Twenty Years Ago Today. In the early 1990's I co-founded a company called Electric Communities. We were going to develop a system for interactive socialization and commerce.


I think what's happening is that when you create person2, the sex and info properties of it refer to those in nestObj. When you reference person2.info, since person2 doesn't redefine the info property, it goes through to the prototype and modifies the object there.

It looks like the "right" way to do it is the way you build person3, so that the object has its own info object to modify and doesn't go up to the prototype.

I'm reading the book too (slowly), so I sympathize with you. :)

Crockford, Crockford, Crockfords or Crockford's may refer to: People[edit]. Alex Crockford, a Harry Potter cast member · Beryl Crockford (1950–2016), British rower; Douglas  Crockford's Clerical Directory. The definitive guide to Anglican clergy and churches in the Church of England, the Church of Ireland, the Church in Wales and the Scottish Episcopal Church, with biographies of over 27,000 Anglican clergy dating back to 1968.


I've changed the examples to give you a better demonstration of what is happening here. Demo

First we create an object with three properties; A number, a string and an object with one property with a string value.

Then we create a second object from the first using Object.create();

var obj1 = { 
    num : 1,
    str : 'foo',
    obj : { less: 'more' }
};
var obj2 = Object.create( obj1 );

console.log( '[1] obj1:', obj1 );
console.log( '[1] obj2:', obj2 );
"[1] obj1:"
[object Object] {
  num: 1,
  obj: [object Object] {
    less: "more"
  },
  str: "foo"
}
"[1] obj2:"
[object Object] {
  num: 1,
  obj: [object Object] {
    less: "more"
  },
  str: "foo"
}

Looks good right? We have our first object and a second copied object.

Not so fast; Let's see what happens when we change some of the values on the first object.

obj1.num = 3;
obj1.str = 'bar';
obj1.obj.less = 'less';

console.log( '[2] obj1:', obj1 );
console.log( '[2] obj2:', obj2 );
"[2] obj1:"
[object Object] {
  num: 3,
  obj: [object Object] {
    less: "less"
  },
  str: "bar"
}
"[2] obj2:"
[object Object] {
  num: 3,
  obj: [object Object] {
    less: "less"
  },
  str: "bar"
}

Now again we have our first object, with changes, and a copy of that object. What's happening here?

Let's check if the objects have their own properties.

for( var prop in obj1 ) console.log( '[3] obj1.hasOwnProperty( ' + prop + ' ): ' + obj1.hasOwnProperty( prop ) );
for( var prop in obj2 ) console.log( '[3] obj2.hasOwnProperty( ' + prop + ' ): ' + obj2.hasOwnProperty( prop ) );
"[3] obj1.hasOwnProperty( num ): true"
"[3] obj1.hasOwnProperty( str ): true"
"[3] obj1.hasOwnProperty( obj ): true"
"[3] obj2.hasOwnProperty( num ): false"
"[3] obj2.hasOwnProperty( str ): false"
"[3] obj2.hasOwnProperty( obj ): false"

obj1 has all of its own properties, just like we defined, but obj2 doesn't.

What happens when we change some of obj2's properties?

obj2.num = 1;
obj2.str = 'baz';
obj2.obj.less = 'more';

console.log( '[4] obj1:', obj1 );
console.log( '[4] obj2:', obj2 );
for( var prop in obj1 ) console.log( '[4] obj1.hasOwnProperty( ' + prop + ' ): ' + obj1.hasOwnProperty( prop ) );
for( var prop in obj2 ) console.log( '[4] obj2.hasOwnProperty( ' + prop + ' ): ' + obj2.hasOwnProperty( prop ) );
"[4] obj1:"
[object Object] {
  num: 3,
  obj: [object Object] {
    less: "more"
  },
  str: "bar"
}
"[4] obj2:"
[object Object] {
  num: 1,
  obj: [object Object] {
    less: "more"
  },
  str: "baz"
}
"[4] obj1.hasOwnProperty( num ): true"
"[4] obj1.hasOwnProperty( str ): true"
"[4] obj1.hasOwnProperty( obj ): true"
"[4] obj2.hasOwnProperty( num ): true"
"[4] obj2.hasOwnProperty( str ): true"
"[4] obj2.hasOwnProperty( obj ): false"

So, num and str changed on obj2 and not on obj1 just like we wanted, but obj1.obj.less changed when it shouldn't have.

From the hasOwnProperty() checks we can see that, even though we changed obj2.obj.less, we didn't set obj2.obj first. This means that we are still referring to obj1.obj.less.

Let's create an object from obj1.obj and assign it to obj2.obj and see if that gives us what we're looking for.

obj2.obj = Object.create( obj1.obj );

console.log( '[5] obj1:', obj1 );
console.log( '[5] obj2:', obj2 );
for( var prop in obj1 ) console.log( '[5] obj1.hasOwnProperty( ' + prop + ' ): ' + obj1.hasOwnProperty( prop ) );
for( var prop in obj2 ) console.log( '[5] obj2.hasOwnProperty( ' + prop + ' ): ' + obj2.hasOwnProperty( prop ) );
"[5] obj1:"
[object Object] {
  num: 3,
  obj: [object Object] {
    less: "more"
  },
  str: "bar"
}
"[5] obj2:"
[object Object] {
  num: 1,
  obj: [object Object] {
    less: "more"
  },
  str: "baz"
}
"[5] obj1.hasOwnProperty( num ): true"
"[5] obj1.hasOwnProperty( str ): true"
"[5] obj1.hasOwnProperty( obj ): true"
"[5] obj2.hasOwnProperty( num ): true"
"[5] obj2.hasOwnProperty( str ): true"
"[5] obj2.hasOwnProperty( obj ): true"

That's good, now obj2 has its own obj property. Let's see what happens when we change obj2.obj.less now.

obj2.obj.less = 'less';

console.log( '[6] obj1:', obj1 );
console.log( '[6] obj2:', obj2 );
"[6] obj1:"
[object Object] {
  num: 3,
  obj: [object Object] {
    less: "more"
  },
  str: "bar"
}
"[6] obj2:"
[object Object] {
  num: 1,
  obj: [object Object] {
    less: "less"
  },
  str: "baz"
}

So what this all tells us is that, if the property has not yet been changed on the created object, any get requests to the created object for that property will be forwarded to the original object.

The set request for obj2.obj.less = 'more' from the previous code block first requires a get request for obj2.obj, which doesn't exist in obj2 at that point, so it forwards to obj1.obj and in turn obj1.obj.less.

Then finally when we read obj2 again, we still haven't set obj2.obj so that get request will be forwarded to obj1.obj and return the setting that we had previously changed, causing the effect that changing a property of the second objects object child seems to change both, but really it is only actually changing the first.


You can use this function to return a new object completely separated from the original recursively.

Demo

var obj1 = { 
    num : 1,
    str : 'foo',
    obj : { less: 'more' }
};
var obj2 = separateObject( obj1 );

function separateObject( obj1 ) {

    var obj2 = Object.create( Object.getPrototypeOf( obj1 ) );
    for(var prop in obj1) {
        if( typeof obj1[prop] === "object" )
            obj2[prop] = separateObject( obj1[prop] );
        else
            obj2[prop] = obj1[prop];
    }

    return obj2;
}

console.log( '[1] obj1:', obj1 );
console.log( '[1] obj2:', obj2 );
for( var prop in obj1 ) console.log( '[1] obj1.hasOwnProperty( ' + prop + ' ): ' + obj1.hasOwnProperty( prop ) );
for( var prop in obj2 ) console.log( '[1] obj2.hasOwnProperty( ' + prop + ' ): ' + obj2.hasOwnProperty( prop ) );
"[1] obj1:"
[object Object] {
  num: 1,
  obj: [object Object] {
    less: "more"
  },
  str: "foo"
}
"[1] obj2:"
[object Object] {
  num: 1,
  obj: [object Object] {
    less: "more"
  },
  str: "foo"
}
"[1] obj1.hasOwnProperty( num ): true"
"[1] obj1.hasOwnProperty( str ): true"
"[1] obj1.hasOwnProperty( obj ): true"
"[1] obj2.hasOwnProperty( num ): true"
"[1] obj2.hasOwnProperty( str ): true"
"[1] obj2.hasOwnProperty( obj ): true"

Let's see what happens when we change some variables now.

obj1.num = 3;
obj1.str = 'bar';
obj1.obj.less = 'less';

console.log( '[2] obj1:', obj1 );
console.log( '[2] obj2:', obj2 );
"[2] obj1:"
[object Object] {
  num: 3,
  obj: [object Object] {
    less: "less"
  },
  str: "bar"
}
"[2] obj2:"
[object Object] {
  num: 1,
  obj: [object Object] {
    less: "more"
  },
  str: "foo"
}

Everything works exactly the way you expected it to.

Douglas Crockford, Douglas Crockford douglascrockford. I was born in Frostbite Falls, Minnesota. I left when I was 6 months old because it was too damn cold. My latest book is  2020-02-06 add and subtract. If you only have the bitwise operations, you can implement addition. function add(augend, addend) { const sum = augend ^ addend; const


douglascrockford (Douglas Crockford) · GitHub, Crockford's code snippet demonstrating an alternative looks so much neater and slicker, it should have the standards committee jointly slapping  Crockford-Pigeon Mountain WMA This 20,657-acre state-owned Wildlife Management Area is in Northwest Georgia (Walker County). This area features abundant wildlife, miles of trails, unique natural features, and gorgeous views.


The Wun Show: Douglas Crockford has been sniffing JavaScript's , "Advanced JavaScript"), Douglas Crockford explores not only the language as it is today Duration: 1:49:55 Posted: Aug 25, 2011 Susan Janet Crockford (born 1954) is a Canadian author and blogger who writes about zoology and climate science, specializing in Holocene mammals.From 2004 to 2019 she was an adjunct professor in Anthropology at the University of Victoria.


Douglas Crockford: The JavaScript Programming Language , Crockford on JavaScript. YUI Library; 8 videos; 122,347 views; Last updated on Jun 29, 2014. Play all. Share. Loading Save  Crockfords is the most exclusive Casino in Mayfair, where you can game in unsurpassed luxury. This London Casino is a favourite of discerning patrons