Crockford’s Prototypal Inheritance – Probleme mit verschachtelten Objekten

Lesezeit: 9 Minuten

Crockfords Prototypal Inheritance Probleme mit verschachtelten Objekten
1nendlich

Ich habe „Javascript: The Good Parts“ von Douglas Crockford gelesen – und obwohl es ein bisschen extrem ist, bin ich mit vielem, was er zu sagen hat, an Bord.

In Kapitel 3 diskutiert er Objekte und legt an einer Stelle ein Muster (auch hier gefunden) zur Vereinfachung und Vermeidung einiger Verwirrungen/Probleme, die mit der Verwendung des integrierten Schlüsselworts „neu“ einhergehen.

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

Also habe ich versucht, dies in einem Projekt zu verwenden, an dem ich arbeite, und ich habe ein Problem festgestellt, als ich versuchte, von verschachtelten Objekten zu erben. Wenn ich einen Wert eines verschachtelten Objekts überschreibe, das mit diesem Muster geerbt wurde, überschreibt es das verschachtelte Element ganz nach oben in der Prototypkette.

Crockfords Beispiel ist wie das flatObj im folgenden Beispiel, das gut funktioniert. Das Verhalten ist jedoch bei verschachtelten Objekten inkonsistent:

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

(auch auf einem Geige)

Mache ich etwas falsch oder ist das eine Einschränkung dieses Musters?

  • verwandt: JavaScript Object.create — vererbt verschachtelte Eigenschaften

    – Bergi

    8. Juni 2014 um 13:00 Uhr

1647273307 797 Crockfords Prototypal Inheritance Probleme mit verschachtelten Objekten
Bergi

Es gibt keine Inkonsistenz. Denken Sie nur nicht an verschachtelte Objekte: a Direkte Eigenschaft eines Objekts ist immer entweder sein Prototyp oder eine eigene Eigenschaft. Dabei ist es unerheblich, ob der Eigenschaftswert ein Primitiv oder ein Objekt ist.

Also, wenn du es tust

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

child.x wird auf dasselbe Objekt verweisen wie parent.x – Das hier {a:0} Objekt. Und wenn Sie eine Eigenschaft davon ändern:

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

beide werden davon betroffen sein. Um eine “verschachtelte” Eigenschaft unabhängig zu ändern, müssen Sie zuerst ein unabhängiges Objekt erstellen:

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

Was Sie tun können, ist

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

das heißt, sie sind nicht absolut unabhängig – aber immer noch zwei verschiedene Objekte.

  • Das klärt die Dinge ziemlich viel – und ich verstehe das Problem jetzt. Danke 🙂 denn alle Objekte sind Referenzen… Duh. Noch weniger bequem auf diese Weise. Nun ja.

    – 1nendlich

    12. April 2012 um 20:59 Uhr

  • „Eine direkte Eigenschaft eines Objekts liegt immer entweder an seinem Prototyp oder an einer eigenen Eigenschaft“ – Für mich bedeutet eine direkte Eigenschaft die eigene Eigenschaft eines Objekts, und die in seiner Prototypkette könnten aufgerufen werden indirekt. Können Sie erklären, was genau Sie mit direktem Eigentum meinen, oder was ein ist indirekt Eigentum..?

    – TJ

    18. November 2015 um 19:55 Uhr


  • @TJ Ich glaube, ich habe den Begriff “direkt” verwendet, um hier das Gegenteil von “verschachtelt” zu bedeuten. Sozusagen eine einzelne Ebene in einer Eigenschaftskette.

    – Bergi

    18. November 2015 um 19:59 Uhr


Ich denke, was passiert, ist das, wenn Sie etwas erschaffen person2der sex und info Eigenschaften davon beziehen sich auf die in nestObj. Wenn Sie referenzieren person2.infoseit person2 definiert das nicht neu info Eigenschaft, geht es bis zum Prototyp durch und modifiziert dort das Objekt.

Es sieht so aus, als ob der “richtige” Weg, es zu tun, die Art und Weise ist, wie Sie bauen person3damit das Objekt seine eigene hat info zu modifizierendes Objekt und reicht nicht bis zum Prototyp.

Ich lese das Buch auch (langsam), also sympathisiere ich mit Ihnen. 🙂

Ich habe die Beispiele geändert, um Ihnen eine bessere Demonstration dessen zu geben, was hier passiert. Demo

Zuerst erstellen wir ein Objekt mit drei Eigenschaften; Eine Zahl, eine Zeichenfolge und ein Objekt mit einer Eigenschaft mit einem Zeichenfolgenwert.

Dann erstellen wir ein zweites Objekt aus der ersten Verwendung 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"
}

Sieht gut aus, oder? Wir haben unser erstes Objekt und ein zweites kopiertes Objekt.

Nicht so schnell; Mal sehen, was passiert, wenn wir einige der Werte für das erste Objekt ändern.

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

Jetzt haben wir wieder unser erstes Objekt mit Änderungen und eine Kopie dieses Objekts. Was passiert hier?

Lassen Sie uns überprüfen, ob die Objekte ihre eigenen Eigenschaften haben.

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 hat alle seine eigenen Eigenschaften, genau wie wir sie definiert haben, aber obj2 nicht.

Was passiert, wenn wir einige ändern obj2Eigenschaften?

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"

Damit, num und str eingeschaltet obj2 und nicht an obj1 so wie wir wollten, aber obj1.obj.less geändert, wenn es nicht hätte sein sollen.

Von dem hasOwnProperty() Kontrollen können wir das sehen, obwohl wir uns geändert haben obj2.obj.lesswir haben nicht gesetzt obj2.obj Erste. Dies bedeutet, dass wir uns immer noch auf beziehen obj1.obj.less.

Lassen Sie uns ein Objekt aus erstellen obj1.obj und zuordnen obj2.obj und sehen, ob das uns das gibt, wonach wir suchen.

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"

Das ist gut, jetzt obj2 hat sein eigenes obj Eigentum. Mal sehen, was passiert, wenn wir wechseln obj2.obj.less jetzt.

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

Das alles sagt uns also, dass, wenn die Eigenschaft am erstellten Objekt noch nicht geändert wurde, alle get Anforderungen an das erstellte Objekt für diese Eigenschaft werden an das ursprüngliche Objekt weitergeleitet.

Die set Anfrage für obj2.obj.less="more" aus dem vorherigen Codeblock erfordert zunächst a get Anfrage für obj2.objdie es in nicht gibt obj2 an diesem Punkt, also leitet es weiter obj1.obj und wiederum obj1.obj.less.

Dann endlich, wenn wir lesen obj2 wieder, wir haben immer noch nicht festgelegt obj2.obj damit get Anfrage wird weitergeleitet an obj1.obj und geben die Einstellung zurück, die wir zuvor geändert hatten, was den Effekt verursacht, dass das Ändern einer Eigenschaft des zweiten Objekts, Objektkind, beide zu ändern scheint, aber in Wirklichkeit nur das erste ändert.


Sie können diese Funktion verwenden, um ein neues Objekt vollständig getrennt vom Original rekursiv zurückzugeben.

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"

Mal sehen, was passiert, wenn wir jetzt einige Variablen ändern.

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

Alles funktioniert genau so, wie Sie es erwartet haben.

  • Welche Konsole macht console.log Objekte wie diese, ohne Vererbungsstrukturen zu zeigen?

    – Bergi

    24. August 2015 um 1:17 Uhr

  • Zum separateObject Ich würde empfehlen Object.create(Object.getPrototypeOf(obj1))

    – Bergi

    24. August 2015 um 1:18 Uhr

  • Ich habe die Ausgabe von JSBins Konsole kopiert. Ich habe es versucht Object.getPrototypeOf(obj1) aber es gab ein leeres Objekt zurück. Übersehe ich etwas? ich

    Benutzer4639281

    24. August 2015 um 4:38 Uhr

  • Natürlich tut es das, das ist der Punkt – obj1 hat einen leeren Prototyp und sollte es auch obj2. Sie müssen die Schleife reparieren und tun for (var prop in obj1) obwohl.

    – Bergi

    24. August 2015 um 12:54 Uhr

  • Ich verstehe, worauf du jetzt hinauswillst. Ich habe meine Antwort aktualisiert.

    Benutzer4639281

    24. August 2015 um 17:54 Uhr

1002240cookie-checkCrockford’s Prototypal Inheritance – Probleme mit verschachtelten Objekten

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy