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

IT技术 javascript prototypal-inheritance
2021-01-27 14:41:44

我一直在阅读道格拉斯·克罗克福德 (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

(也在小提琴上

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

3个回答

没有不一致之处。只是不要考虑嵌套对象:对象的直接属性总是在其原型或自己的属性上。属性值是原始值还是对象无关紧要。

所以,当你做

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

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

“一个对象的直接属性总是在它的原型上或一个自己的属性上” - 对我来说,直接属性意味着一个对象自己的属性,它的原型链中的那些可以被称为indirect你能解释一下你所说的直接财产究竟是什么意思,或者什么是间接财产......?
2021-03-14 14:41:44
@TJ 我想我在这里使用了“直接”一词来表示“嵌套”的反义词。可以说是属性链中的单个级别。
2021-03-16 14:41:44
这使事情变得相当清楚-我现在明白了这个问题。谢谢 :) 因为所有对象都是引用... 呃。这样还是不太方便。那好吧。
2021-04-01 14:41:44

我认为发生的事情是,当您创建时person2,它的sexinfo属性指的是nestObj. 当您引用 时person2.info,由于person2没有重新定义info属性,它会进入原型并在那里修改对象。

看起来做这件事的“正确”方法是您构建的方式person3,以便对象有自己的info对象要修改并且不会上升到原型。

我也在看这本书(慢慢地),所以我很同情你。:)

我已经更改了示例,以便更好地演示此处发生的情况。 Demo

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

然后我们使用第一个对象创建第二个对象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"

所以,numstr改变了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请求都将转发到原始对象。

set用于请求obj2.obj.less = 'more'从前面的代码块,首先需要一个get用于请求obj2.obj,其不存在于obj2在该点处,所以它转发到obj1.obj和反过来obj1.obj.less

然后最后当我们obj2再次阅读时,我们仍然没有设置obj2.obj以便将get请求转发到obj1.obj并返回我们之前更改的设置,从而导致更改第二个对象 object child 的属性似乎同时更改了两个,但实际上它实际上只是在改变第一个。


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

Demo

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

一切都完全按照您的预期工作。

我从 JSBin 的控制台复制了输出。我刚刚尝试过,Object.getPrototypeOf(obj1)但它返回了一个空对象。我错过了什么吗?一世
2021-03-13 14:41:44
什么控制台会做这样的console.log对象,而不显示继承结构?
2021-03-16 14:41:44
当然可以,这就是重点 -obj1有一个空的原型,所以应该obj2您需要修复循环并执行for (var prop in obj1)
2021-03-22 14:41:44
因为separateObject我推荐Object.create(Object.getPrototypeOf(obj1))
2021-03-24 14:41:44
我明白你现在得到了什么。我已经更新了我的答案。
2021-03-26 14:41:44