[英]Crockford's Prototypal inheritance - Issues with nested objects
我一直在阅读道格拉斯·克罗克福德 (Douglas Crockford) 所著的“Javascript: The Good Parts”——虽然它有点极端,但我对他要说的很多内容都持赞同态度。
在第 3 章中,他讨论了对象,并有一次展示了一种模式(也可在此处找到)以简化和避免使用内置“new”关键字时出现的一些混淆/问题。
if (typeof Object.create !== 'function') {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
newObject = Object.create(oldObject);
所以我尝试在我正在处理的项目中使用它,并且在尝试从嵌套的对象继承时发现了一个问题。 如果我覆盖使用此模式继承的嵌套对象的值,它会一直覆盖原型链上的嵌套元素。
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;
两者都会受到影响。 要独立更改“嵌套”属性,您首先必须创建一个独立对象:
child.x = {a:0};
child.x.a = 1;
parent.x.a; // still 0
你能做的是
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
这意味着它们不是绝对独立的——但仍然是两个不同的对象。
我认为发生的事情是,当您创建person2
,它的sex
和info
属性指的是nestObj
那些。 当您引用person2.info
,由于person2
没有重新定义info
属性,它会通过原型并在那里修改对象。
看起来“正确”的方法是您构建person3
的方式,以便对象有自己的info
对象要修改并且不会上升到原型。
我也在看这本书(慢慢地),所以我很同情你。 :)
我已经更改了示例,以便更好地演示此处发生的情况。 演示
首先我们创建一个具有三个属性的对象; 一个数字、一个字符串和一个具有一个字符串值的属性的对象。
然后我们使用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"
}
看起来不错吧? 我们有第一个对象和第二个复制对象。
没那么快; 让我们看看当我们更改第一个对象上的某些值时会发生什么。
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
创建一个对象并将它分配给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"
}
所以这一切告诉我们的是,如果创建的对象上的属性尚未更改,则对该属性的创建对象的任何get
请求都将转发到原始对象。
上一个代码块中对obj2.obj.less = 'more'
的set
请求首先需要对obj2.obj
的get
请求, obj2.obj
obj2
中不存在该请求,因此它转发到obj1.obj
并依次转发给obj1.obj.less
。
那么最后我们再次读取obj2
时候,仍然没有设置obj2.obj
这样get
请求就会被转发到obj1.obj
并返回我们之前修改过的设置,造成改变了第二个objects对象的一个属性的效果child 似乎同时改变了两者,但实际上它只是真正改变了第一个。
您可以使用此函数以递归方式返回与原始对象完全分离的新对象。
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"
现在让我们看看当我们改变一些变量时会发生什么。
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"
}
一切都按照您预期的方式工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.