Javascript Crockford的原型继承-嵌套对象的问题

Javascript Crockford的原型继承-嵌套对象的问题,javascript,prototypal-inheritance,Javascript,Prototypal Inheritance,我一直在读Douglas Crockford的《Javascript:The Good Parts》——虽然有点极端,但我同意他说的很多话 在第3章中,他讨论了对象,并在某一点上提出了一种模式,以简化和避免使用内置的new关键字带来的一些混乱/问题 if (typeof Object.create !== 'function') { Object.create = function (o) { function F() {} F.prototype = o

我一直在读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时,它的性别和信息属性指的是nestObj中的属性。当您引用person2.info时,由于person2没有重新定义info属性,因此它会进入原型并在那里修改对象

看起来正确的方法是构建person3,这样对象就有了自己的信息对象可以修改,而不会升级到原型


我读这本书读得太慢了,所以我同情你

我更改了示例,以便更好地演示这里发生的事情

首先,我们创建一个具有三个属性的对象;一个数字、一个字符串和一个具有一个字符串值的属性的对象

然后我们使用object.create从第一个对象创建第二个对象

变量obj1={ 数目:1,, str:‘foo’, obj:{less:'more'} }; var obj2=Object.create obj1; console.log“[1]obj1:”,obj1; console.log“[1]obj2:”,obj2; [1] obj1: [对象]{ 数目:1,, obj:[对象]{ 减:多 }, str:foo } [1] obj2: [对象]{ 数目:1,, obj:[对象]{ 减:多 }, str:foo } 看起来不错吧?我们有第一个对象和第二个复制对象

不要那么快;让我们看看当我们改变第一个对象上的一些值时会发生什么

obj1.num=3; obj1.str='bar'; obj1.obj.less=‘less’; console.log“[2]obj1:”,obj1; console.log“[2]obj2:”,obj2; [2] obj1: [对象]{ 数字:3, obj:[对象]{ 少:少 }, str:bar } [2] obj2: [对象]{ 数字:3, obj:[对象]{ 少:少 }, str:bar } 现在我们又有了我们的第一个对象,带有更改,以及该对象的一个副本。这里发生了什么事

让我们检查对象是否有自己的属性

对于obj1 console.log“[3]obj1.hasOwnProperty”+prop+”:“+obj1.hasOwnProperty prop; 对于obj2 console.log“[3]obj2.hasOwnProperty”+prop+”:“+obj2.hasOwnProperty prop; [3] obj1.hasOwnProperty编号:true [3] obj1.hasOwnProperty str:true [3] obj1.hasOwnProperty obj:真 [3] obj2.hasOwnProperty num:false [3] obj2.hasOwnProperty str:false [3] obj2.hasOwnProperty对象:false obj1有它自己的所有属性,就像我们定义的那样,但obj2没有

当我们更改obj2的某些属性时会发生什么

obj2.num=1; obj2.str='baz'; obj2.obj.less='更多'; console.log“[4]obj1:”,obj1; console.log“[4]obj2:”,obj2; 对于obj1 console.log“[4]obj1.hasOwnProperty”+prop+”:“+obj1.hasOwnProperty prop; 对于obj2 console.log“[4]obj2.hasOwnProperty”+prop+”:“+obj2.hasOwnProperty prop; [4] obj1: [对象]{ 数字:3, obj:[对象]{ 减:多 }, str:bar } [4] obj2: [对象]{ 数目:1,, obj:[对象]{ 减:多 }, str:baz } [4] obj1.hasOwnProperty编号:true [4] obj1.hasOwnProperty str:true [4] obj1.hasOwnProperty obj:真 [4] obj2.hasOwnProperty num:true [4] obj2.hasOwnProperty str:true [4] obj2.hasOwnProperty对象:false 所以,num和str在obj2上改变了,而不是像我们希望的那样在obj1上改变了,但是obj1.obj.less在不应该改变的时候改变了

从hasOwnProperty检查中我们可以看到,尽管我们更改了obj2.obj.less,但我们没有首先设置obj2.obj。这意味着我们仍然是指obj1.obj.less

让我们创建一个 对象,并将其分配给obj2.obj,看看这是否为我们提供了所需的内容

obj2.obj=Object.create obj1.obj; console.log“[5]obj1:”,obj1; console.log“[5]obj2:”,obj2; 对于obj1 console.log“[5]obj1.hasOwnProperty”+prop+”:“+obj1.hasOwnProperty prop; 对于obj2 console.log“[5]obj2.hasOwnProperty”+prop+”:“+obj2.hasOwnProperty prop; [5] obj1: [对象]{ 数字:3, obj:[对象]{ 减:多 }, str:bar } [5] obj2: [对象]{ 数目:1,, obj:[对象]{ 减:多 }, str:baz } [5] obj1.hasOwnProperty编号:true [5] obj1.hasOwnProperty str:true [5] obj1.hasOwnProperty obj:真 [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: [对象]{ 数字:3, obj:[对象]{ 减:多 }, str:bar } [6] obj2: [对象]{ 数目:1,, obj:[对象]{ 少:少 }, str:baz } 这一切告诉我们的是,如果创建的对象上的属性尚未更改,那么对该属性创建的对象的任何get请求都将转发到原始对象

上一个代码块中的set request for obj2.obj.less='more'首先需要对obj2.obj的get请求,该请求在obj2中不存在,因此它将转发到obj1.obj,然后再转发到obj1.obj.less

最后,当我们再次读取obj2时,我们仍然没有设置obj2.obj,以便get请求将转发到obj1.obj,并返回我们之前更改的设置,导致更改第二个对象子对象的属性似乎会同时更改这两个对象,但实际上只会更改第一个对象

可以使用此函数递归地返回与原始对象完全分离的新对象

变量obj1={ 数目:1,, str:‘foo’, obj:{less:'more'} }; var obj2=分离对象obj1; 函数分隔对象对象J1{ var obj2=Object.create Object.getPrototypeOf obj1; obj1中的forvar prop{ 如果obj1[prop]==对象的类型 obj2[prop]=分离对象obj1[prop]; 其他的 obj2[prop]=obj1[prop]; } 返回obj2; } console.log“[1]obj1:”,obj1; console.log“[1]obj2:”,obj2; 对于obj1 console.log“[1]obj1.hasOwnProperty”+prop+”:“+obj1.hasOwnProperty prop; 对于obj2 console.log“[1]obj2.hasOwnProperty”+prop+”:“+obj2.hasOwnProperty prop; [1] obj1: [对象]{ 数目:1,, obj:[对象]{ 减:多 }, str:foo } [1] obj2: [对象]{ 数目:1,, obj:[对象]{ 减:多 }, str:foo } [1] obj1.hasOwnProperty编号:true [1] obj1.hasOwnProperty str:true [1] obj1.hasOwnProperty obj:真 [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: [对象]{ 数字:3, obj:[对象]{ 少:少 }, str:bar } [2] obj2: [对象]{ 数目:1,, obj:[对象]{ 减:多 }, str:foo }
一切都完全按照您预期的方式运行。

这就澄清了很多问题-我现在理解了这个问题。谢谢:因为所有对象都是引用。。。嗯。这样更不方便。哦,好吧,一个对象的直接属性总是在它的原型或自己的属性上——对我来说,直接属性意味着一个对象自己的属性,而它的原型链中的那些属性可以称为间接属性。你能解释一下你所说的直接属性到底是什么意思,或者什么是间接属性吗?@TJ我想我用了直接这个词来表示这里嵌套的相反意思。属性链中的单个级别,也就是说.related:console.log对象是什么控制台,而不显示继承结构?对于separateObject,我建议使用Object.createObject.getPrototypeObObj1i复制JSBin控制台的输出。我刚刚尝试了Object.getPrototypeOfobj1,但它返回了一个空对象。我错过什么了吗?当然,这就是要点——obj1有一个空的原型,obj2也应该是空的。不过,您需要修复循环并在obj1中为var prop执行操作。我知道您现在得到了什么。我已经更新了我的答案。