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?
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()
;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.
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.
obj1
has all of its own properties, just like we defined, butobj2
doesn't.What happens when we change some of
obj2
's properties?So,
num
andstr
changed onobj2
and not onobj1
just like we wanted, butobj1.obj.less
changed when it shouldn't have.From the
hasOwnProperty()
checks we can see that, even though we changedobj2.obj.less
, we didn't setobj2.obj
first. This means that we are still referring toobj1.obj.less
.Let's create an object from
obj1.obj
and assign it toobj2.obj
and see if that gives us what we're looking for.That's good, now
obj2
has its ownobj
property. Let's see what happens when we changeobj2.obj.less
now.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 forobj2.obj.less = 'more'
from the previous code block first requires aget
request forobj2.obj
, which doesn't exist inobj2
at that point, so it forwards toobj1.obj
and in turnobj1.obj.less
.Then finally when we read
obj2
again, we still haven't setobj2.obj
so thatget
request will be forwarded toobj1.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
Let's see what happens when we change some variables now.
Everything works exactly the way you expected it to.
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
child.x
will be referencing the same object asparent.x
- that one{a:0}
object. And when you change a property of it:both will be affected. To change a "nested" property independently, you first will have to create an independent object:
What you can do is
which means they are not absolutely independent - but still two different objects.
I think what's happening is that when you create
person2
, thesex
andinfo
properties of it refer to those innestObj
. When you referenceperson2.info
, sinceperson2
doesn't redefine theinfo
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 owninfo
object to modify and doesn't go up to the prototype.I'm reading the book too (slowly), so I sympathize with you. :)