Warum ist das Erweitern nativer Objekte eine schlechte Praxis?


135

Jeder JS-Meinungsführer sagt, dass das Erweitern der nativen Objekte eine schlechte Praxis ist. Aber wieso? Bekommen wir einen Performance-Hit? Befürchten sie, dass jemand es "falsch" macht und unzählige Typen hinzufügt Object, wodurch praktisch alle Schleifen eines Objekts zerstört werden?

Nehmen TJ Holowaychuk ‚s should.js zum Beispiel. Er fügt ein einfaches Getter zu Objectund alles funktioniert ( Quelle ).

Object.defineProperty(Object.prototype, 'should', {
  set: function(){},
  get: function(){
    return new Assertion(Object(this).valueOf());
  },
  configurable: true
});

Das macht wirklich Sinn. Zum Beispiel könnte man verlängern Array.

Array.defineProperty(Array.prototype, "remove", {
  set: function(){},
  get: function(){
    return removeArrayElement.bind(this);
  }
});
var arr = [0, 1, 2, 3, 4];
arr.remove(3);

Gibt es Argumente gegen die Erweiterung nativer Typen?


9
Was erwarten Sie, wenn später ein natives Objekt so geändert wird, dass es eine "Entfernen" -Funktion mit einer anderen Semantik als Ihrer eigenen enthält? Sie kontrollieren den Standard nicht.
ta.speot.is

1
Es ist nicht dein einheimischer Typ. Es ist jedermanns einheimischer Typ.

5
"Befürchten sie, dass jemand es" falsch "macht und dem Objekt unzählige Typen hinzufügt, wodurch praktisch alle Schleifen eines Objekts zerstört werden?" : Ja. In den Tagen, als diese Meinung gebildet wurde, war es unmöglich, nicht aufzählbare Eigenschaften zu schaffen. Jetzt mögen die Dinge in dieser Hinsicht anders sein, aber stellen Sie sich vor, jede Bibliothek erweitert native Objekte so, wie sie es wollen. Es gibt einen Grund, warum wir angefangen haben, Namespaces zu verwenden.
Felix Kling

5
Für das, was es wert ist, denken einige "Meinungsführer", zum Beispiel Brendan Eich, dass es vollkommen in Ordnung ist, die einheimischen Prototypen zu erweitern.
Benjamin Gruenbaum

2
Foo sollte heutzutage nicht global sein, wir haben Includejs, Commonjs usw.
Jamie Pate

Antworten:


126

Wenn Sie ein Objekt erweitern, ändern Sie sein Verhalten.

Das Ändern des Verhaltens eines Objekts, das nur von Ihrem eigenen Code verwendet wird, ist in Ordnung. Wenn Sie jedoch das Verhalten von etwas ändern, das auch von anderem Code verwendet wird, besteht das Risiko, dass Sie diesen anderen Code beschädigen.

Wenn Sie den Objekt- und Array-Klassen in Javascript Methoden hinzufügen, ist das Risiko, dass etwas beschädigt wird, aufgrund der Funktionsweise von Javascript sehr hoch. Langjährige Erfahrung hat mich gelehrt, dass diese Art von Zeug alle Arten von schrecklichen Fehlern in Javascript verursacht.

Wenn Sie ein benutzerdefiniertes Verhalten benötigen, ist es weitaus besser, eine eigene Klasse (möglicherweise eine Unterklasse) zu definieren, als eine native zu ändern. Auf diese Weise werden Sie überhaupt nichts kaputt machen.

Die Fähigkeit, die Funktionsweise einer Klasse zu ändern, ohne sie zu unterordnen, ist ein wichtiges Merkmal jeder guten Programmiersprache, das jedoch selten und mit Vorsicht verwendet werden muss.


2
Das Hinzufügen von so etwas .stgringify()würde also als sicher angesehen werden?
Buschtoens

7
Es gibt viele Probleme, zum Beispiel, was passiert, wenn ein anderer Code ebenfalls versucht, eine eigene stringify()Methode mit unterschiedlichem Verhalten hinzuzufügen ? Es ist wirklich nichts, was Sie in der täglichen Programmierung tun sollten ... nicht, wenn Sie nur hier und da ein paar Zeichen Code speichern möchten. Definieren Sie besser Ihre eigene Klasse oder Funktion, die Eingaben akzeptiert und sie stringifiziert. Die meisten Browser definieren JSON.stringify(), am besten überprüfen Sie, ob dies vorhanden ist, und wenn nicht, definieren Sie es selbst.
Abhi Beckert

5
Ich ziehe es someError.stringify()vor errors.stringify(someError). Es ist unkompliziert und passt perfekt zum Konzept von js. Ich mache etwas, das speziell an ein bestimmtes ErrorObject gebunden ist.
Buschtoens

1
Das einzige (aber gute) Argument, das bleibt, ist, dass einige riesige Makro-Bibliotheken beginnen könnten, Ihre Typen zu übernehmen und sich gegenseitig stören könnten. Oder gibt es noch etwas?
Buschtoens

4
Wenn das Problem darin besteht, dass Sie möglicherweise eine Kollision haben, können Sie ein Muster verwenden, bei dem Sie jedes Mal, wenn Sie dies tun, immer bestätigen, dass die Funktion undefiniert ist, bevor Sie sie definieren? Und wenn Sie Bibliotheken von Drittanbietern immer vor Ihren eigenen Code stellen, sollten Sie solche Kollisionen immer erkennen können.
Dave Cousineau

32

Es gibt keinen messbaren Nachteil wie einen Leistungseinbruch. Zumindest erwähnte niemand etwas. Das ist also eine Frage persönlicher Vorlieben und Erfahrungen.

Das Hauptargument: Es sieht besser aus und ist intuitiver: Syntaxzucker. Da es sich um eine typ- / instanzspezifische Funktion handelt, sollte sie speziell an diesen Typ / diese Instanz gebunden sein.

Das Hauptgegenargument: Code kann stören. Wenn lib A eine Funktion hinzufügt, kann es die Funktion von lib B überschreiben. Dies kann den Code sehr leicht beschädigen.

Beide haben einen Punkt. Wenn Sie sich auf zwei Bibliotheken verlassen, die Ihre Typen direkt ändern, erhalten Sie höchstwahrscheinlich fehlerhaften Code, da die erwartete Funktionalität wahrscheinlich nicht dieselbe ist. Da stimme ich voll und ganz zu. Makrobibliotheken dürfen die nativen Typen nicht manipulieren. Andernfalls werden Sie als Entwickler nie wissen, was wirklich hinter den Kulissen vor sich geht.

Und das ist der Grund, warum ich Bibliotheken wie jQuery, Unterstrich usw. nicht mag. Versteh mich nicht falsch; Sie sind absolut gut programmiert und wirken wie ein Zauber, aber sie sind groß . Sie verwenden nur 10% von ihnen und verstehen ungefähr 1%.

Deshalb bevorzuge ich einen atomistischen Ansatz , bei dem Sie nur das benötigen, was Sie wirklich brauchen. Auf diese Weise wissen Sie immer, was passiert. Die Mikrobibliotheken tun nur das, was Sie möchten, damit sie nicht stören. Wenn der Endbenutzer weiß, welche Funktionen hinzugefügt werden, kann die Erweiterung nativer Typen als sicher angesehen werden.

TL; DR Erweitern Sie im Zweifelsfall keine nativen Typen. Erweitern Sie einen nativen Typ nur, wenn Sie zu 100% sicher sind, dass der Endbenutzer dieses Verhalten kennt und möchte. Bearbeiten Sie in keinem Fall die vorhandenen Funktionen eines nativen Typs, da dies die vorhandene Schnittstelle beschädigen würde.

Wenn Sie den Typ erweitern möchten, verwenden Sie Object.defineProperty(obj, prop, desc);Wenn Sie nicht können , verwenden Sie die Typen prototype.


Ich habe diese Frage ursprünglich gestellt, weil ich wollte Error, dass s über JSON gesendet werden kann. Also brauchte ich einen Weg, um sie zu fesseln. error.stringify()fühlte sich viel besser an als errorlib.stringify(error); Wie das zweite Konstrukt andeutet, arbeite ich an errorlibund nicht an sich errorselbst.


Ich bin offen für weitere Meinungen dazu.
Buschtoens

4
Bedeuten Sie, dass jQuery und der Unterstrich native Objekte erweitern? Sie tun es nicht. Wenn Sie sie aus diesem Grund meiden, irren Sie sich.
JLRishe

1
Hier ist eine Frage, die meines Erachtens in dem Argument übersehen wird, dass das Erweitern nativer Objekte mit lib A zu Konflikten mit lib B führen kann: Warum sollten Sie zwei Bibliotheken haben, die entweder so ähnlich oder so breit sind, dass dieser Konflikt möglich ist? Dh ich wähle entweder lodash oder unterstreiche nicht beides. Unsere derzeitige Bibliothekslandschaft in Javascript ist so überfüllt und Entwickler (im Allgemeinen) werden so nachlässig, wenn sie sie hinzufügen, dass wir am Ende Best Practices vermeiden, um unsere Lib Lords zu beschwichtigen
micahblu

2
@micahblu - Eine Bibliothek könnte beschließen, ein Standardobjekt nur zum Zweck der eigenen internen Programmierung zu ändern (dies ist häufig der Grund, warum Benutzer dies tun möchten). Dieser Konflikt wird also nicht vermieden, nur weil Sie nicht zwei Bibliotheken mit derselben Funktion verwenden würden.
jfriend00

1
Sie können auch (ab es2015) eine neue Klasse erstellen, die eine vorhandene Klasse erweitert, und diese in Ihrem eigenen Code verwenden. Also, wenn MyError extends Errores eine stringifyMethode haben kann , ohne mit anderen Unterklassen zu kollidieren. Sie müssen immer noch Fehler behandeln, die nicht von Ihrem eigenen Code generiert wurden, daher ist dies möglicherweise weniger nützlich Errorals bei anderen.
ShadSterling

16

Wenn Sie es von Fall zu Fall betrachten, sind möglicherweise einige Implementierungen akzeptabel.

String.prototype.slice = function slice( me ){
  return me;
}; // Definite risk.

Das Überschreiben bereits erstellter Methoden verursacht mehr Probleme als es löst, weshalb in vielen Programmiersprachen häufig angegeben wird, diese Praxis zu vermeiden. Woher sollen Entwickler wissen, dass die Funktion geändert wurde?

String.prototype.capitalize = function capitalize(){
  return this.charAt(0).toUpperCase() + this.slice(1);
}; // A little less risk.

In diesem Fall überschreiben wir keine bekannte JS-Kernmethode, sondern erweitern String. Ein Argument in diesem Beitrag erwähnte, wie der neue Entwickler wissen soll, ob diese Methode Teil des Kern-JS ist oder wo die Dokumente zu finden sind. Was würde passieren, wenn das Kernobjekt JS String eine Methode mit dem Namen großschreiben erhalten würde? ?

Was wäre, wenn Sie anstelle von Namen, die möglicherweise mit anderen Bibliotheken kollidieren, einen firmen- / app-spezifischen Modifikator verwenden würden, den alle Entwickler verstehen könnten?

String.prototype.weCapitalize = function weCapitalize(){
  return this.charAt(0).toUpperCase() + this.slice(1);
}; // marginal risk.

var myString = "hello to you.";
myString.weCapitalize();
// => Hello to you.

Wenn Sie andere Objekte weiter erweitern würden, würden alle Entwickler sie in freier Wildbahn mit (in diesem Fall) uns begegnen , wodurch sie benachrichtigt würden, dass es sich um eine unternehmens- / app-spezifische Erweiterung handelt.

Dies eliminiert keine Namenskollisionen, verringert jedoch die Möglichkeit. Wenn Sie feststellen, dass das Erweitern von JS-Kernobjekten für Sie und / oder Ihr Team bestimmt ist, ist dies möglicherweise für Sie.


15

Meiner Meinung nach ist es eine schlechte Praxis. Der Hauptgrund ist die Integration. Zitieren sollte.js docs:

OMG IT EXTENDS OBJECT ???!?! @ Ja, ja, mit einem einzigen Getter sollte, und nein, es wird Ihren Code nicht brechen

Wie kann der Autor das wissen? Was ist, wenn mein Spott-Framework dasselbe tut? Was ist, wenn meine Versprechen lib dasselbe tun?

Wenn Sie es in Ihrem eigenen Projekt tun, ist es in Ordnung. Aber für eine Bibliothek ist es ein schlechtes Design. Underscore.js ist ein Beispiel dafür, was richtig gemacht wurde:

var arr = [];
_(arr).flatten()
// or: _.flatten(arr)
// NOT: arr.flatten()

2
Ich bin sicher, TJs Antwort wäre, diese Versprechen oder spöttischen Rahmenbedingungen nicht zu verwenden: x
Jim Schubert

Das _(arr).flatten()Beispiel hat mich tatsächlich überzeugt, native Objekte nicht zu erweitern. Mein persönlicher Grund dafür war rein syntaktisch. Aber das befriedigt mein ästhetisches Gefühl :) Selbst wenn ich einen normaleren Funktionsnamen verwende foo(native).coolStuff(), um ihn in ein "erweitertes" Objekt umzuwandeln, sieht das syntaktisch großartig aus. Also danke dafür!
zB

7

Die Erweiterung von Prototypen von Einbauten ist in der Tat eine schlechte Idee. Mit ES2015 wurde jedoch eine neue Technik eingeführt, mit der das gewünschte Verhalten erzielt werden kann:

Verwenden von WeakMaps zum Verknüpfen von Typen mit integrierten Prototypen

Die folgende Implementierung erweitert die Prototypen Numberund Arrayohne sie zu berühren:

// new types

const AddMonoid = {
  empty: () => 0,
  concat: (x, y) => x + y,
};

const ArrayMonoid = {
  empty: () => [],
  concat: (acc, x) => acc.concat(x),
};

const ArrayFold = {
  reduce: xs => xs.reduce(
   type(xs[0]).monoid.concat,
   type(xs[0]).monoid.empty()
)};


// the WeakMap that associates types to prototpyes

types = new WeakMap();

types.set(Number.prototype, {
  monoid: AddMonoid
});

types.set(Array.prototype, {
  monoid: ArrayMonoid,
  fold: ArrayFold
});


// auxiliary helpers to apply functions of the extended prototypes

const genericType = map => o => map.get(o.constructor.prototype);
const type = genericType(types);


// mock data

xs = [1,2,3,4,5];
ys = [[1],[2],[3],[4],[5]];


// and run

console.log("reducing an Array of Numbers:", ArrayFold.reduce(xs) );
console.log("reducing an Array of Arrays:", ArrayFold.reduce(ys) );
console.log("built-ins are unmodified:", Array.prototype.empty);

Wie Sie sehen, können mit dieser Technik auch primitive Prototypen erweitert werden. Es verwendet eine Kartenstruktur undObject Identität, um Typen mit integrierten Prototypen zu verknüpfen.

In meinem Beispiel wird eine reduceFunktion aktiviert, die nur ein Arrayeinzelnes Argument erwartet , da sie die Informationen zum Erstellen eines leeren Akkumulators und zum Verketten von Elementen mit diesem Akkumulator aus den Elementen des Arrays selbst extrahieren kann.

Bitte beachten Sie, dass ich den normalen MapTyp hätte verwenden können , da schwache Referenzen keinen Sinn machen, wenn sie lediglich eingebaute Prototypen darstellen, die niemals durch Müll gesammelt werden. A WeakMapist jedoch nicht iterierbar und kann nur überprüft werden, wenn Sie den richtigen Schlüssel haben. Dies ist eine gewünschte Funktion, da ich jegliche Form der Typreflexion vermeiden möchte.


Dies ist eine coole Verwendung von WeakMap. Seien Sie vorsichtig damit bei xs[0]leeren Arrays. type(undefined).monoid ...
Vielen Dank, dass Sie

@naomik Ich weiß - siehe meine letzte Frage das 2. Code-Snippet.

Wie standardmäßig ist die Unterstützung für diesen @ftor?
Onassar

4
-1; Was bringt das alles? Sie erstellen lediglich ein separates globales Wörterbuch, das Typen Methoden zuordnet, und suchen dann explizit nach Methoden in diesem Wörterbuch nach Typ. Infolgedessen verlieren Sie die Unterstützung für die Vererbung (eine auf Object.prototypediese Weise "hinzugefügte" Methode kann nicht für eine aufgerufen werden Array), und die Syntax ist erheblich länger / hässlicher, als wenn Sie einen Prototyp wirklich erweitert hätten. Es wäre fast immer einfacher, nur Dienstprogrammklassen mit statischen Methoden zu erstellen. Der einzige Vorteil dieses Ansatzes ist die begrenzte Unterstützung des Polymorphismus.
Mark Amery

3
@ MarkAmery Hey Kumpel, du verstehst es nicht. Ich verliere nicht die Vererbung, sondern werde sie los. Die Vererbung ist so 80er Jahre, dass Sie es vergessen sollten. Der springende Punkt dieses Hacks ist die Nachahmung von Typklassen, die einfach ausgedrückt überladene Funktionen sind. Es gibt jedoch eine berechtigte Kritik: Ist es nützlich, Typklassen in Javascript zu imitieren? Nein, ist es nicht. Verwenden Sie lieber eine typisierte Sprache, die sie nativ unterstützt. Ich bin mir ziemlich sicher, dass Sie das gemeint haben.

7

Ein Grund mehr, warum Sie nicht sollten native Objekte erweitern :

Wir verwenden Magento, das prototype.js verwendet und viele Dinge auf nativen Objekten erweitert. Dies funktioniert einwandfrei, bis Sie sich für neue Funktionen entscheiden und hier große Probleme auftreten.

Wir haben Webcomponents auf einer unserer Seiten eingeführt, daher beschließt die webcomponents-lite.js, das gesamte (native) Ereignisobjekt im IE zu ersetzen (warum?). Dies bricht natürlich prototype.js, was wiederum Magento bricht. (Bis Sie das Problem gefunden haben, können Sie viele Stunden investieren, um es zurückzuverfolgen.)

Wenn Sie Ärger mögen, machen Sie weiter!


4

Ich sehe drei Gründe, dies nicht zu tun ( zumindest innerhalb einer Bewerbung ), von denen nur zwei in den vorhandenen Antworten hier angesprochen werden:

  1. Wenn Sie es falsch machen, fügen Sie versehentlich allen Objekten des erweiterten Typs eine aufzählbare Eigenschaft hinzu. Einfach zu umgehen Object.defineProperty, wodurch standardmäßig nicht aufzählbare Eigenschaften erstellt werden.
  2. Möglicherweise verursachen Sie einen Konflikt mit einer von Ihnen verwendeten Bibliothek. Kann mit Sorgfalt vermieden werden; Überprüfen Sie einfach, welche Methoden die von Ihnen verwendeten Bibliotheken definieren, bevor Sie einem Prototyp etwas hinzufügen, überprüfen Sie die Versionshinweise beim Upgrade und testen Sie Ihre Anwendung.
  3. Möglicherweise verursachen Sie einen Konflikt mit einer zukünftigen Version der nativen JavaScript-Umgebung.

Punkt 3 ist wohl der wichtigste. Durch Testen können Sie sicherstellen, dass Ihre Prototyp-Erweiterungen keine Konflikte mit den von Ihnen verwendeten Bibliotheken verursachen, da Sie entscheiden, welche Bibliotheken Sie verwenden. Dies gilt nicht für native Objekte, vorausgesetzt, Ihr Code wird in einem Browser ausgeführt. Wenn Sie Array.prototype.swizzle(foo, bar)heute definieren und morgen Google Array.prototype.swizzle(bar, foo)zu Chrome hinzufügt , werden Sie wahrscheinlich mit einigen verwirrten Kollegen enden, die sich fragen, warum.swizzle das Verhalten nicht mit dem übereinstimmt, was auf MDN dokumentiert ist.

(Siehe auch die Geschichte, wie das Herumspielen von Mootools mit Prototypen, die sie nicht besaßen, die Umbenennung einer ES6-Methode erzwang, um eine Unterbrechung des Webs zu vermeiden .)

Dies kann vermieden werden, indem ein anwendungsspezifisches Präfix für Methoden verwendet wird, die nativen Objekten hinzugefügt werden (z. B. definieren Array.prototype.myappSwizzlestatt Array.prototype.swizzle), aber das ist irgendwie hässlich. Es ist genauso gut lösbar, indem eigenständige Dienstprogrammfunktionen verwendet werden, anstatt Prototypen zu erweitern.


2

Perf ist auch ein Grund. Manchmal müssen Sie möglicherweise die Schlüssel durchlaufen. Es gibt verschiedene Möglichkeiten, dies zu tun

for (let key in object) { ... }
for (let key in object) { if (object.hasOwnProperty(key) { ... } }
for (let key of Object.keys(object)) { ... }

Ich benutze normalerweise, for of Object.keys()weil es das Richtige tut und relativ knapp ist, ohne den Scheck hinzufügen zu müssen.

Aber es ist viel langsamer .

for-of vs for-in perf Ergebnisse

Nur zu erraten, dass der Grund Object.keyslangsam ist, ist offensichtlich, Object.keys()muss eine Zuordnung vornehmen. Tatsächlich muss AFAIK seitdem eine Kopie aller Schlüssel zuweisen.

  const before = Object.keys(object);
  object.newProp = true;
  const after = Object.keys(object);

  before.join('') !== after.join('')

Es ist möglich, dass die JS-Engine eine Art unveränderliche Schlüsselstruktur verwendet, sodass Object.keys(object)eine Referenz zurückgegeben wird, die über unveränderliche Schlüssel iteriertobject.newProp ein völlig neues Objekt für unveränderliche Schlüssel erstellt, aber was auch immer, es ist deutlich langsamer als bis zu 15x

Sogar überprüfen hasOwnProperty ist bis zu 2x langsamer.

Der Sinn all dessen ist, dass Sie, wenn Sie über einen perfekter Code verfügen und Schlüssel durchlaufen müssen, diese verwenden können möchten, for inohne anrufen zu müssenhasOwnProperty . Sie können dies nur tun, wenn Sie nichts geändert habenObject.prototype

Beachten Sie, dass wenn Sie Object.definePropertyden Prototyp ändern, wenn die von Ihnen hinzugefügten Elemente nicht aufzählbar sind, sie das Verhalten von JavaScript in den oben genannten Fällen nicht beeinflussen. Zumindest in Chrome 83 beeinträchtigen sie leider die Leistung.

Geben Sie hier die Bildbeschreibung ein

Ich habe 3000 nicht aufzählbare Eigenschaften hinzugefügt, um zu versuchen, das Auftreten von Perf-Problemen zu erzwingen. Mit nur 30 Eigenschaften waren die Tests zu nahe, um festzustellen, ob es Auswirkungen auf die Leistung gab.

https://jsperf.com/does-adding-non-enumerable-properties-affect-perf

Firefox 77 und Safari 13.1 zeigten keinen Unterschied in der Leistung zwischen den Klassen Augmented und Unaugmented. Möglicherweise wird v8 in diesem Bereich behoben, und Sie können die Leistungsprobleme ignorieren.

Aber lassen Sie mich auch hinzufügen, dass es die Geschichte von gibtArray.prototype.smoosh . Die Kurzversion ist Mootools, eine beliebte Bibliothek, die selbst erstellt wurde Array.prototype.flatten. Als das Standardkomitee versuchte, einen Eingeborenen hinzuzufügen, stellten Array.prototype.flattensie fest, dass dies nicht möglich war, ohne viele Websites zu beschädigen. Die Entwickler, die von der Pause erfuhren, schlugen vor, die es5-Methode smooshals Witz zu bezeichnen, aber die Leute flippten aus, ohne zu verstehen, dass es ein Witz war. Sie entschieden sich flatstattdessen fürflatten

Die Moral der Geschichte ist, dass Sie native Objekte nicht erweitern sollten. Wenn Sie dies tun, könnten Sie auf dasselbe Problem stoßen, bei dem Ihre Inhalte beschädigt werden. Wenn Ihre bestimmte Bibliothek nicht so beliebt ist wie MooTools, ist es unwahrscheinlich, dass die Browser-Anbieter das von Ihnen verursachte Problem umgehen. Wenn Ihre Bibliothek so populär wird, wäre es eine Art Mittel, alle anderen dazu zu zwingen, das von Ihnen verursachte Problem zu umgehen. Bitte erweitern Sie native Objekte nicht


Warten Sie, um zu hasOwnPropertyerfahren, ob sich die Eigenschaft im Objekt oder im Prototyp befindet. Mit der for inSchleife erhalten Sie jedoch nur aufzählbare Eigenschaften. Ich habe es gerade ausprobiert: Fügen Sie dem Array-Prototyp eine nicht aufzählbare Eigenschaft hinzu, die nicht in der Schleife angezeigt wird. Sie müssen den Prototyp nicht ändern, damit die einfachste Schleife funktioniert.
ygoe

Aber aus anderen Gründen ist es immer noch eine schlechte Praxis;)
gman
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.