Heim  >  Artikel  >  Web-Frontend  >  Tiefes Verständnis der Deep-Copy-Leistung von JavaScript

Tiefes Verständnis der Deep-Copy-Leistung von JavaScript

小云云
小云云Original
2018-02-03 14:28:232035Durchsuche

Dieser Artikel teilt Ihnen hauptsächlich die Analyse der JavaScript-Deep-Copy-Leistung mit. Wie kopiere ich ein Objekt in JavaScript? Das ist eine sehr einfache Frage, aber die Antwort ist nicht einfach.

Wenn Sie nicht wissen, was das bedeutet, schauen Sie sich das folgende Beispiel an:

function mutate(obj) {
  obj.a = true;
}

const obj = {a: false};
mutate(obj)
console.log(obj.a); // 输出 true

Die Funktion mutate ändert ihre Argumente. Im Szenario Wertübergabe ist der formale Parameter der Funktion nur eine Kopie des tatsächlichen Parameters – eine Kopie – und der tatsächliche Parameter wird nach Abschluss des Funktionsaufrufs nicht geändert. Aber in JavaScript, wo als Referenz übergeben, verweisen die formalen Parameter und tatsächlichen Parameter der Funktion auf dasselbe Objekt. Wenn die formalen Parameter innerhalb der Parameter geändert werden, werden auch die tatsächlichen Parameter außerhalb der Funktion geändert.

In einigen Fällen müssen Sie das Originalobjekt beibehalten. In diesem Fall müssen Sie eine Kopie des Originalobjekts an die Funktion übergeben, um zu verhindern, dass die Funktion das Originalobjekt ändert.

Flache Kopie: Object.assign()

Eine einfache Möglichkeit, eine Kopie eines Objekts zu erhalten, ist die Verwendung von Object.assign(target, sources...). Es akzeptiert eine beliebige Anzahl von Quellobjekten, zählt alle ihre Eigenschaften auf und weist sie target zu. Wenn wir ein neues leeres Objekt target verwenden, können wir das Objekt kopieren.

const obj = /* ... */;
const copy = Object.assign({}, obj);

Dies ist jedoch nur eine flache Kopie. Wenn unsere Objekte andere Objekte als Eigenschaften enthalten, behalten sie gemeinsame Referenzen bei, was nicht das ist, was wir wollen:

function mutateDeepObject(obj) {
  obj.a.thing = true;
}

const obj = {a: {thing: false}};
const copy = Object.assign({}, obj);
mutateDeepObject(copy)
console.log(obj.a.thing); // prints true
Object.assign Die Methode kopiert nur das Quellobjekt selbst und kann aufgezählt werden Eigenschaften zum Zielobjekt . Diese Methode verwendet das [[Get]] des Quellobjekts und das [[Set]] des Zielobjekts und ruft daher die relevanten getter und setter auf. Daher weist es Eigenschaften zu, anstatt nur neue Eigenschaften zu kopieren oder zu definieren. Wenn die Zusammenführungsquelle getter enthält, ist sie möglicherweise nicht zum Zusammenführen neuer Eigenschaften in den Prototyp geeignet. Um eine Eigenschaftsdefinition (einschließlich ihrer Aufzählbarkeit) in einen Prototyp zu kopieren, sollten Object.getOwnPropertyDescriptor() und Object.defineProperty() verwendet werden.

Und was nun? Es gibt mehrere Möglichkeiten, eine tiefe Kopie eines Objekts zu erstellen.

Hinweis: Vielleicht hat jemand die Objektdestrukturierungsoperation erwähnt, bei der es sich ebenfalls um eine flache Kopie handelt.

JSON.parse

Eine der ältesten Möglichkeiten, eine Kopie eines Objekts zu erstellen, besteht darin, dieses Objekt in seine JSON-String-Darstellung umzuwandeln und es dann wieder in das Objekt zu analysieren. Das fühlt sich etwas bedrückend an, aber es funktioniert:

const obj = /* ... */;
const copy = JSON.parse(JSON.stringify(obj));

Der Nachteil hierbei ist, dass Sie einen temporären, möglicherweise großen String erstellen, nur um ihn wieder in den Parser einzufügen. Ein weiterer Nachteil besteht darin, dass diese Methode keine zyklischen Objekte verarbeiten kann. Und es kommt häufig vor, dass sich Objekte in einer Schleife befinden. Wenn Sie beispielsweise eine baumartige Datenstruktur erstellen, verweist ein Knoten auf seinen übergeordneten Knoten, der wiederum auf seine untergeordneten Knoten verweist.

const x = {};
const y = {x};
x.y = y; // Cycle: x.y.x.y.x.y.x.y.x...
const copy = JSON.parse(JSON.stringify(x)); // throws!

Außerdem gehen integrierte Typen wie Map, Set, RegExp, Date, ArrayBuffer und andere integrierte Typen bei der Serialisierung verloren.

Structured Clone Strukturierter Klonalgorithmus

Strukturiertes Klonen ist ein bestehender Algorithmus zum Übertragen von Werten von einem Ort zum anderen. Es wird beispielsweise immer dann verwendet, wenn Sie postMessage aufrufen, um eine Nachricht an ein anderes Fenster oder WebWorker zu senden. Das Schöne am strukturierten Klonen ist, dass es zyklische Objekte verarbeitet und eine große Anzahl integrierter Typen unterstützt. Das Problem besteht darin, dass der Algorithmus zum Zeitpunkt des Schreibens nicht direkt verfügbar ist, sondern nur als Teil anderer APIs. Ich denke, wir sollten doch wissen, was enthalten ist, oder? . .

MessageChannel

Wie gesagt, es funktioniert, solange Sie den postMessage strukturierten Klonalgorithmus aufrufen. Wir können ein MessageChannel erstellen und eine Nachricht senden. Auf der Empfängerseite enthält die Nachricht einen strukturierten Klon unseres ursprünglichen Datenobjekts.

function structuralClone(obj) {
  return new Promise(resolve => {
    const {port1, port2} = new MessageChannel();
    port2.onmessage = ev => resolve(ev.data);
    port1.postMessage(obj);
  });
}

const obj = /* ... */;
const clone = await structuralClone(obj);

Der Nachteil dieses Ansatzes besteht darin, dass er asynchron ist. Das ist zwar in Ordnung, aber manchmal müssen Sie ein Objekt synchron tief kopieren.

History API

Wenn Sie jemals eine SPA mit history.pushState() geschrieben haben, wissen Sie, dass Sie ein Statusobjekt bereitstellen können, um eine URL zu speichern . Es stellt sich heraus, dass dieses Statusobjekt strukturiertes Klonen verwendet – und es ist synchron. Wir müssen aufpassen, dass die von der Programmlogik verwendeten Statusobjekte nicht durcheinander gebracht werden, daher müssen wir nach dem Klonen den ursprünglichen Status wiederherstellen. Um Überraschungen zu vermeiden, verwenden Sie history.replaceState() anstelle von history.pushState().

function structuralClone(obj) {
  const oldState = history.state;
  history.replaceState(obj, document.title);
  const copy = history.state;
  history.replaceState(oldState, document.title);
  return copy;
}

const obj = /* ... */;
const clone = structuralClone(obj);

Allerdings fühlt es sich etwas übertrieben an, die Engine des Browsers nur zum Kopieren eines Objekts zu verwenden. Darüber hinaus begrenzt der Safari-Browser die Anzahl der replaceState-Aufrufe auf 100 Mal in 30 Sekunden.

Benachrichtigungs-API

Nach einem Tweet zeigte mir Jeremy Banks eine dritte Möglichkeit, strukturiertes Klonen zu nutzen: die Benachrichtigungs-API.

function structuralClone(obj) {
  return new Notification('', {data: obj, silent: true}).data;
}

const obj = /* ... */;
const clone = structuralClone(obj);

Kurz und prägnant. Ich liebe es!

Es erfordert jedoch einen Berechtigungsmechanismus im Browser, daher vermute ich, dass es sehr langsam ist. Aus irgendeinem Grund gibt Safari immer undefined zurück.

Leistungsextravaganz

Ich möchte messen, welche Methode die höchste Leistung bietet. In meinem ersten (naiven) Versuch habe ich ein kleines JSON-Objekt genommen und das Objekt tausendmal auf unterschiedliche Weise geklont. Glücklicherweise hat mir Mathias Bynens erzählt, dass V8 über einen Cache verfügt, wenn Sie einem Objekt Eigenschaften hinzufügen. Also vergleiche ich den Cache. Um sicherzustellen, dass ich nie auf den Cache stoße, habe ich eine Funktion geschrieben, die ein Objekt einer bestimmten Tiefe und Breite mit einem zufälligen Schlüsselnamen generiert, und den Test erneut ausgeführt.

Diagramm!

Hier sehen Sie die Leistung verschiedener Technologien in Chrome, Firefox und Edge. Je niedriger, desto besser.

Tiefes Verständnis der Deep-Copy-Leistung von JavaScript

Tiefes Verständnis der Deep-Copy-Leistung von JavaScript

Tiefes Verständnis der Deep-Copy-Leistung von JavaScript

Fazit

Was haben wir also daraus gemacht?

  • Wenn Sie keine Schleifenobjekte haben und keine integrierten Typen beibehalten müssen, können Sie die schnellste Klonleistung mit dem browserübergreifenden JSON.parse(JSON.stringify()) erzielen hat mich wirklich überrascht.

  • Wenn Sie einen ordnungsgemäß strukturierten Klon wünschen, ist MessageChannel Ihre einzige zuverlässige browserübergreifende Option.

Wäre es besser, wenn die Browserplattform direkt eine structuredClone()-Funktion bereitstellen würde? Das glaube ich auf jeden Fall, die neueste HTML-Spezifikation diskutiert dieses Synchronous clone = global.structuredClone(value, transfer = []) API · Issue #793 · whatwg/html.

Verwandte Empfehlungen:

jQuerys $.extend Shallow Copy- und Deep Copy-Beispielanalyse

Erzielen Sie Deep Copy in jquery Copy und flache Kopie

Was sind flache und tiefe Kopien in Js

Das obige ist der detaillierte Inhalt vonTiefes Verständnis der Deep-Copy-Leistung von JavaScript. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn