克罗克福德的原型inheritance – 嵌套对象的问题

我一直在阅读道格拉斯·克罗克福德(Douglas Crockford)的“Javascript:The Good Parts” – 尽pipe有点极端,但我还是有很多他要说的。

在第三章中,他讨论了一些对象,并在一个地方列出了一个模式( 在这里也可以find ),以简化和避免使用内置的“新”关键字带来的一些混淆/问题。

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

所以我尝试了在我正在使用的项目中使用这个,并且在尝试从嵌套对象inheritance时注意到了一个问题。 如果我使用这个模式覆盖了一个嵌套对象的值,那么它会覆盖原型链上的嵌套元素。

Crockford的例子就像下面例子中的flatObj ,效果很好。 但是,该行为与嵌套对象不一致:

 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 

(也在小提琴上 )

我做错了什么,还是这种模式的限制?

没有不一致。 只是不要想到嵌套对象:对象的直接属性总是在其原型或自己的属性。 这个属性重视一个原始的或一个对象是无关紧要的。

所以,当你这样做

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

child.x将引用与parent.x相同的对象 – 即一个{a:0}对象。 而当你改变它的属性:

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

两者都会受到影响。 要单独更改“嵌套”属性,首先必须创build一个独立的对象:

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

你可以做的是

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

这意味着他们不是绝对独立的 – 但仍然是两个不同的对象。

我认为发生的事情是,当你创buildperson2 ,它的sexinfo属性是指在nestObj那些nestObj 。 当您引用person2.info ,由于person2不重新定义info属性,它将转到原型并修改其中的对象。

看起来像“正确”的做法是你build立person3的方式,所以这个对象有自己的info对象来修改,而不是原型。

我正在慢慢读这本书,所以我同情你。 🙂

我已经改变了这些例子,让你更好地展示这里发生的事情。 演示

首先我们创build一个具有三个属性的对象; 一个数字,一个string和一个具有一个string值的属性的对象。

然后我们使用Object.create()从第一个对象创build第二个对象。

 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" } 

看起来不错吧? 我们有我们的第一个对象和第二个复制的对象。

没那么快 让我们看看当我们改变第一个对象的一些值时会发生什么。

 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" } 

现在我们再次有我们的第一个对象,更改和该对象的副本。 这里发生了什么?

让我们来检查一下对象是否有自己的属性。

 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拥有它自己的所有属性,就像我们定义的一样,但是obj2没有。

当我们改变一些obj2的属性会发生什么?

 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" 

所以, numstr改变了obj2而不是obj1 ,就像我们想要的,但obj1.obj.less改变了,当它不应该有。

hasOwnProperty()检查我们可以看到,即使我们改变了obj2.obj.less ,我们并没有首先设置obj2.obj 。 这意味着我们仍然指obj1.obj.less

让我们从obj1.obj创build一个对象,并将其分配给obj2.obj ,看看是否给我们什么,我们正在寻找。

 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" 

这很好,现在obj2有它自己的obj属性。 让我们看看现在当我们改变obj2.obj.less时会发生什么。

 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" } 

那么这个告诉我们的是,如果属性在创build的对象上还没有被改变,任何对那个属性创build的对象的请求都会被转发到原来的对象。

从前面的代码块中set obj2.obj.less = 'more'的请求首先需要obj2.objget请求, obj2.obj在该点不存在obj2 ,所以它转发给obj1.obj ,然后obj1.obj.less

最后当我们再次读取obj2时候,我们还没有设置obj2.obj这样get请求就会被转发到obj1.obj并且返回之前改变的设置,导致改变第二个对象的属性小孩似乎改变了,但实际上它只是实际上改变了第一个。


你可以使用这个函数来返回一个完全与原始分离的新对象。

演示

 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" 

让我们看看现在改变一些variables会发生什么。

 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" } 

一切都按照您预期的方式工作。