Maison > interface Web > js tutoriel > Compréhension approfondie des performances de copie approfondie de JavaScript

Compréhension approfondie des performances de copie approfondie de JavaScript

小云云
Libérer: 2018-02-03 14:28:23
original
2089 Les gens l'ont consulté

Cet article partage principalement avec vous l'analyse des performances de copie profonde de JavaScript. Comment copier un objet en JavaScript ? C’est une question très simple, mais la réponse n’est pas simple.

Si vous ne savez pas ce que cela signifie, jetez un œil à l'exemple suivant :

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

const obj = {a: false};
mutate(obj)
console.log(obj.a); // 输出 true
Copier après la connexion

La fonction mutate change ses arguments. Dans le scénario pass-by-value, le paramètre formel de la fonction est simplement une copie du paramètre réel - une copie - et le paramètre réel n'est pas modifié lorsque l'appel de fonction est terminé. Mais en JavaScript, où passer par référence, les paramètres formels et les paramètres réels de la fonction pointent vers le même objet. Lorsque les paramètres formels sont modifiés à l'intérieur des paramètres, les paramètres réels en dehors de la fonction sont également modifiés.

Donc, dans certains cas, vous devez conserver l'objet d'origine. Dans ce cas, vous devez transmettre une copie de l'objet d'origine dans la fonction pour empêcher la fonction de modifier l'objet d'origine.

Copie superficielle : Object.assign()

Un moyen simple d'obtenir une copie d'un objet est d'utiliser Object.assign(target, sources...). Il accepte n'importe quel nombre d'objets source, énumère toutes leurs propriétés et les attribue à target. Si nous utilisons un nouvel objet vide target, alors nous pouvons copier l'objet.

const obj = /* ... */;
const copy = Object.assign({}, obj);
Copier après la connexion

Cependant, il ne s'agit que d'une copie superficielle. Si nos objets contiennent d'autres objets comme propriétés, ils garderont des références partagées, ce qui n'est pas ce que nous souhaitons :

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
Copier après la connexion
Object.assign La méthode copiera uniquement l'objet source lui-même et pourra énumérer propriétés pour cibler l'objet . Cette méthode utilise le [[Get]] de l'objet source et le [[Set]] de l'objet cible, elle appelle donc les getter et setter pertinents. Par conséquent, il attribue des propriétés plutôt que de simplement copier ou définir de nouvelles propriétés. Si la source de fusion contient getter, cela peut la rendre inappropriée pour fusionner de nouvelles propriétés dans le prototype. Afin de copier une définition de propriété (y compris son énumérabilité) dans un prototype, Object.getOwnPropertyDescriptor() et Object.defineProperty() doivent être utilisés.

Et maintenant ? Il existe plusieurs façons de créer une copie complète d'un objet.

Remarque : Peut-être que quelqu'un a mentionné l'opération de déstructuration d'objet, qui est également une copie superficielle.

JSON.parse

L'une des façons les plus anciennes de créer une copie d'un objet consiste à convertir cet objet en sa représentation sous forme de chaîne JSON, puis à l'analyser à nouveau dans l'objet. Cela semble un peu oppressant, mais cela fonctionne :

const obj = /* ... */;
const copy = JSON.parse(JSON.stringify(obj));
Copier après la connexion

L'inconvénient ici est que vous créez une chaîne temporaire, potentiellement volumineuse, juste pour la remettre dans l'analyseur. Un autre inconvénient est que cette méthode ne peut pas gérer les objets cycliques. Et les objets en boucle se produisent souvent. Par exemple, lorsque vous créez une structure de données arborescente, un nœud fait référence à son parent, qui à son tour fait référence à ses enfants.

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!
Copier après la connexion

De plus, les types intégrés tels que Map, Set, RegExp, Date, ArrayBuffer et autres sont perdus lors de la sérialisation.

Clone structuré Algorithme de clonage structuré

Le clonage structuré est un algorithme existant permettant de transférer des valeurs d'un endroit à un autre. Par exemple, il est utilisé chaque fois que vous appelez postMessage pour envoyer un message à une autre fenêtre ou WebWorker. L’avantage du clonage structuré est qu’il gère les objets cycliques et prend en charge un grand nombre de types intégrés. Le problème est qu’au moment de la rédaction de cet article, l’algorithme n’est pas disponible directement, uniquement dans le cadre d’autres API. Je suppose que nous devrions savoir ce qui est inclus, n'est-ce pas. . .

MessageChannel

Comme je l'ai dit, cela fonctionne tant que vous appelez l'algorithme de clonage structuré postMessage. Nous pouvons créer un MessageChannel et envoyer un message. À la réception, le message contient un clone structuré de notre objet de données d'origine.

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);
Copier après la connexion

L'inconvénient de cette approche est qu'elle est asynchrone. Bien que cela soit acceptable, vous devez parfois copier en profondeur un objet de manière synchrone.

API History

Si vous avez déjà écrit un SPA en utilisant history.pushState(), vous savez que vous pouvez fournir un objet d'état pour enregistrer une URL . Il s'avère que cet objet d'état utilise le clonage structuré - et il est synchrone. Nous devons faire attention à ne pas gâcher les objets d'état utilisés par la logique du programme, nous devons donc restaurer l'état d'origine une fois le clonage terminé. Pour éviter toute surprise, utilisez history.replaceState() au lieu de 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);
Copier après la connexion

Cependant, utiliser le moteur du navigateur uniquement pour copier un objet semble un peu excessif. De plus, le navigateur Safari limite le nombre d'appels replaceState à 100 fois en 30 secondes.

API de notification

Après un tweet, Jeremy Banks m'a montré une troisième façon d'exploiter le clonage structuré : l'API de notification.

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

const obj = /* ... */;
const clone = structuralClone(obj);
Copier après la connexion

Court et concis. Je l'aime!

Cependant, cela nécessite un mécanisme d'autorisation dans le navigateur, donc je soupçonne qu'il est très lent. Pour une raison quelconque, Safari revient toujours undefined.

Extravagance des performances

Je souhaite mesurer quelle méthode a la performance la plus élevée. Lors de ma première tentative (naïve), j'ai pris un petit objet JSON et l'ai cloné mille fois de différentes manières. Heureusement, Mathias Bynens m'a expliqué que la V8 dispose d'un cache lorsqu'on ajoute des propriétés à un objet. Je compare donc le cache. Pour m'assurer de ne jamais accéder au cache, j'ai écrit une fonction qui génère un objet d'une profondeur et d'une largeur données avec un nom de clé aléatoire et j'ai réexécuté le test.

Graphique !

Voici les performances des différentes technologies dans Chrome, Firefox et Edge. Plus c'est bas, mieux c'est.

Compréhension approfondie des performances de copie approfondie de JavaScript

Compréhension approfondie des performances de copie approfondie de JavaScript

Compréhension approfondie des performances de copie approfondie de JavaScript

Conclusion

Alors qu'est-ce qu'on en a retiré ?

  • Si vous n'avez pas d'objets de boucle et n'avez pas besoin de conserver les types intégrés, vous pouvez obtenir les performances de clonage les plus rapides en utilisant le multi-navigateur JSON.parse(JSON.stringify()), qui m'a vraiment surpris.

  • Si vous souhaitez un clone correctement structuré, MessageChannel est votre seule option multi-navigateur fiable.

Serait-il mieux si la plateforme du navigateur fournissait directement une fonction structuredClone() ? Je le pense certainement, la dernière spécification HTML discute de cette API Synchronous clone = global.structuredClone(value, transfer = []) · Numéro 793 · whatwg/html.

Recommandations associées :

Analyse d'exemples de copie superficielle et de copie profonde $.extend de JQuery

Réaliser une copie approfondie dans jquery Copy et copie superficielle

Que sont les copies superficielles et profondes en Js

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal