克罗克福德的原型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
,它的sex
和info
属性是指在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"
所以, num
和str
改变了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.obj
的get
请求, 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" }
一切都按照您预期的方式工作。