portaldacalheta.pt
  • Principal
  • Rise Of Remote
  • Outils Et Tutoriels
  • Équipes Distribuées
  • Mode De Vie
Interface Web

Chaînes de prototypes JavaScript, chaînes de portée et performances: ce que vous devez savoir



JavaScript: plus qu'il n'y paraît

JavaScript peut sembler être une langue très facile à apprendre au début. C'est peut-être à cause de sa syntaxe flexible. Ou peut-être est-ce à cause de sa similitude avec d’autres langages bien connus comme Java. Ou peut-être est-ce parce qu'il contient si peu de types de données par rapport à des langages comme Java, Ruby ou .NET.

Mais en vérité, JavaScript est beaucoup moins simpliste et plus nuancé que la plupart développeurs réaliser initialement. Même pour développeurs avec plus d'expérience , certaines des fonctionnalités les plus importantes de JavaScript continuent d'être mal comprises et prêtent à confusion. L'une de ces caractéristiques est la façon dont les recherches de données (propriétés et variables) sont effectuées et les ramifications de performances JavaScript à prendre en compte.



En JavaScript, les recherches de données sont régies par deux choses: héritage prototypique et chaîne de portée . En tant que développeur, il est essentiel de bien comprendre ces deux mécanismes, car cela peut améliorer la structure, et souvent les performances, de votre code.



Recherches de propriétés à travers la chaîne de prototypes

Lors de l’accès à une propriété dans un langage basé sur un prototype tel que JavaScript, une recherche dynamique implique différentes couches au sein de l’arborescence prototypique de l’objet.



En JavaScript, chaque fonction est un objet. Lorsqu'une fonction est appelée avec le new opérateur, un nouvel objet est créé. Par exemple:

function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } var p1 = new Person('John', 'Doe'); var p2 = new Person('Robert', 'Doe');

Dans l'exemple ci-dessus, p1 et p2 sont deux objets différents, chacun créé à l'aide de Person fonctionne comme un constructeur. Ce sont des instances indépendantes de Person, comme le montre cet extrait de code:



console.log(p1 instanceof Person); // prints 'true' console.log(p2 instanceof Person); // prints 'true' console.log(p1 === p2); // prints 'false'

Les fonctions JavaScript étant des objets, elles peuvent avoir des propriétés. Une propriété particulièrement importante de chaque fonction est appelée prototype.

prototype, qui est lui-même un objet, hérite du prototype de son parent, qui hérite du prototype de son parent, et ainsi de suite. Ceci est souvent appelé le chaîne prototype . Object.prototype, qui se trouve toujours à la fin de la chaîne de prototypes (c'est-à-dire en haut de l'arbre d'héritage prototypique), contient des méthodes comme toString(), hasProperty(), isPrototypeOf(), etc.



La relation entre le prototype JavaScript et la chaîne de portée est importante

comment apprendre la programmation c

Le prototype de chaque fonction peut être étendu pour définir ses propres méthodes et propriétés personnalisées.



Lorsque vous instanciez un objet (en appelant la fonction à l'aide de l'opérateur new), il hérite de toutes les propriétés du prototype de cette fonction. Gardez toutefois à l'esprit que ces instances n'auront pas d'accès direct à prototype objet mais uniquement à ses propriétés. Par exemple:

// Extending the Person prototype from our earlier example to // also include a 'getFullName' method: Person.prototype.getFullName = function() { return this.firstName + ' ' + this.lastName; } // Referencing the p1 object from our earlier example console.log(p1.getFullName()); // prints 'John Doe' // but p1 can’t directly access the 'prototype' object... console.log(p1.prototype); // prints 'undefined' console.log(p1.prototype.getFullName()); // generates an error

Il y a un point important et quelque peu subtil ici: même si p1 a été créé avant le getFullName a été définie, il y aura toujours accès car son prototype est le Person prototype.



(Il convient de noter que les navigateurs stockent également une référence au prototype de tout objet dans une propriété __proto__, mais c'est vraiment mauvaise pratique pour accéder directement au prototype via le __proto__ propriété, car elle ne fait pas partie de la norme Spécification du langage ECMAScript , alors ne le faites pas! )

Puisque le p1 instance de Person L’objet lui-même n’a pas d’accès direct à prototype objet, si nous voulons écraser getFullName dans p1, nous le ferions comme suit:



// We reference p1.getFullName, *NOT* p1.prototype.getFullName, // since p1.prototype does not exist: p1.getFullName = function(){ return 'I am anonymous'; }

Maintenant p1 a son propre getFullName propriété. Mais le p2 instance (créée dans notre exemple précédent) fait ne pas posséder une telle propriété qui lui est propre. Par conséquent, en invoquant p1.getFullName() accède au getFullName méthode du p1 l'instance elle-même, en invoquant p2.getFullName() remonte la chaîne de prototypes jusqu'au Person objet prototype à résoudre getFullName:

console.log(p1.getFullName()); // prints 'I am anonymous' console.log(p2.getFullName()); // prints 'Robert Doe'

Découvrez comment P1 et P2 sont liés au prototype Person dans cet exemple de prototype JavaScript.

Une autre chose importante à savoir est qu'il est également possible de dynamiquement changer le prototype d’un objet. Par exemple:

function Parent() { this.someVar = 'someValue'; }; // extend Parent’s prototype to define a 'sayHello' method Parent.prototype.sayHello = function(){ console.log('Hello'); }; function Child(){ // this makes sure that the parent's constructor is called and that // any state is initialized correctly. Parent.call(this); }; // extend Child's prototype to define an 'otherVar' property... Child.prototype.otherVar = 'otherValue'; // ... but then set the Child's prototype to the Parent prototype // (whose prototype doesn’t have any 'otherVar' property defined, // so the Child prototype no longer has ‘otherVar’ defined!) Child.prototype = Object.create(Parent.prototype); var child = new Child(); child.sayHello(); // prints 'Hello' console.log(child.someVar); // prints 'someValue' console.log(child.otherVar); // prints 'undefined'

Lorsque vous utilisez l'héritage prototypique, n'oubliez pas de définir les propriétés dans le prototype après ayant hérité de la classe parente ou spécifié un autre prototype.

Ce diagramme montre un exemple de la relation entre les prototypes JavaScript dans une chaîne de prototypes.

Pour résumer, les recherches de propriétés via la chaîne de prototypes JavaScript fonctionnent comme suit:

  • Si l'objet a une propriété avec le nom donné, cette valeur est renvoyée. (La hasOwnProperty peut être utilisée pour vérifier si un objet a une propriété nommée particulière.)
  • Si l'objet ne possède pas la propriété nommée, le prototype de l'objet est vérifié
  • Le prototype étant également un objet, s’il ne contient pas non plus la propriété, le prototype de son parent est vérifié.
  • Ce processus continue le long de la chaîne de prototypes jusqu'à ce que la propriété soit trouvée.
  • Si Object.prototype est atteinte et ne possède pas non plus la propriété, la propriété est considérée comme undefined.

Comprendre le fonctionnement de l'héritage prototypique et des recherches de propriétés est important en général pour les développeurs, mais il est également essentiel en raison de ses ramifications (parfois importantes) sur les performances JavaScript. Comme mentionné dans la documentation pour V8 (Moteur JavaScript open source haute performance de Google), la plupart des moteurs JavaScript utilisent une structure de données semblable à un dictionnaire pour stocker les propriétés des objets. Chaque accès à une propriété nécessite donc une recherche dynamique dans cette structure de données pour résoudre la propriété. Cette approche rend généralement l'accès aux propriétés en JavaScript beaucoup plus lent que l'accès aux variables d'instance dans des langages de programmation tels que Java et Smalltalk.

Recherches de variables dans la chaîne de portée

Un autre mécanisme de recherche en JavaScript est basé sur la portée.

Pour comprendre comment cela fonctionne, il est nécessaire d'introduire le concept de contexte d'exécution .

En JavaScript, il existe deux types de contextes d'exécution:

  • Contexte global, créé lors du lancement d'un processus JavaScript
  • Contexte local, créé lorsqu'une fonction est appelée

Les contextes d'exécution sont organisés en pile. Au bas de la pile, il y a toujours le contexte global, qui est unique pour chaque programme JavaScript. Chaque fois qu'une fonction est rencontrée, un nouveau contexte d'exécution est créé et poussé en haut de la pile. Une fois que la fonction a fini de s'exécuter, son contexte est sorti de la pile.

Considérez le code suivant:

// global context var message = 'Hello World'; var sayHello = function(n){ // local context 1 created and pushed onto context stack var i = 0; var innerSayHello = function() { // local context 2 created and pushed onto context stack console.log((i + 1) + ': ' + message); // local context 2 popped off of context stack } for (i = 0; i

Dans chaque contexte d'exécution se trouve un objet spécial appelé chaîne de portée qui est utilisé pour résoudre les variables. Une chaîne de portées est essentiellement une pile de portées actuellement accessibles, du contexte le plus immédiat au contexte global. (Pour être un peu plus précis, l'objet en haut de la pile s'appelle un Objet d'activation qui contient des références aux variables locales de la fonction en cours d'exécution, les arguments de la fonction nommée et deux objets «spéciaux»: this et arguments.) Par exemple:

La façon dont la chaîne de portées se rapporte aux objets est décrite dans cet exemple JavaScript.

Notez dans le diagramme ci-dessus comment this pointe vers le window objet par défaut et aussi comment le contexte global contient des exemples d'autres objets tels que console et location.

Lors d'une tentative de résolution de variables via la chaîne de portée, le contexte immédiat est d'abord vérifié pour une variable correspondante. Si aucune correspondance n'est trouvée, l'objet de contexte suivant dans la chaîne de portées est vérifié, et ainsi de suite, jusqu'à ce qu'une correspondance soit trouvée. Si aucune correspondance n'est trouvée, un ReferenceError Est lancé.

Il est également important de noter qu'une nouvelle portée est ajoutée à la chaîne de portées lorsqu'un try-catch bloc ou un with bloc est rencontré. Dans l'un ou l'autre de ces cas, un nouvel objet est créé et placé en haut de la chaîne de portées:

function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; }; function persist(person) { with (person) { // The 'person' object was pushed onto the scope chain when we // entered this 'with' block, so we can simply reference // 'firstName' and 'lastName', rather than person.firstName and // person.lastName if (!firstName) { throw new Error('FirstName is mandatory'); } if (!lastName) { throw new Error('LastName is mandatory'); } } try { person.save(); } catch(error) { // A new scope containing the 'error' object is accessible here console.log('Impossible to store ' + person + ', Reason: ' + error); } } var p1 = new Person('John', 'Doe'); persist(p1);

Pour bien comprendre comment se produisent les recherches de variables basées sur l'étendue, il est important de garder à l'esprit qu'en JavaScript, il n'y a actuellement aucune étendue au niveau du bloc. Par exemple:

for (var i = 0; i <10; i++) { /* ... */ } // 'i' is still in scope! console.log(i); // prints '10'

Dans la plupart des autres langages, le code ci-dessus conduirait à une erreur car la «durée de vie» (c'est-à-dire la portée) de la variable i serait limité au bloc for. En JavaScript, cependant, ce n'est pas le cas. Plutôt, i est ajouté à l'objet d'activation en haut de la chaîne de portée et il y restera jusqu'à ce que cet objet soit supprimé de la portée, ce qui se produit lorsque le contexte d'exécution correspondant est supprimé de la pile. Ce comportement est connu sous le nom de levage variable.

Il est à noter, cependant, que la prise en charge des étendues de niveau bloc fait son chemin dans JavaScript via le nouveau let mot-clé. Le let Le mot-clé est déjà disponible dans JavaScript 1.7 et devrait devenir un mot-clé JavaScript officiellement pris en charge à partir d'ECMAScript 6.

Ramifications des performances JavaScript

La façon dont les recherches de propriétés et de variables, utilisant respectivement la chaîne de prototypes et la chaîne de portées, fonctionnent en JavaScript est l’une des fonctionnalités clés du langage, mais c’est l’une des plus délicates et subtiles à comprendre.

Les opérations de recherche que nous avons décrites dans cet exemple, qu'elles soient basées sur la chaîne de prototypes ou la chaîne de portées, sont répétées chaque heure à laquelle une propriété ou une variable est accédée. Lorsque cette recherche se produit dans des boucles ou d'autres opérations intensives, elle peut avoir des ramifications importantes sur les performances JavaScript, en particulier à la lumière de la nature monothread du langage qui empêche plusieurs opérations de se produire simultanément.

Prenons l'exemple suivant:

var start = new Date().getTime(); function Parent() { this.delta = 10; }; function ChildA(){}; ChildA.prototype = new Parent(); function ChildB(){} ChildB.prototype = new ChildA(); function ChildC(){} ChildC.prototype = new ChildB(); function ChildD(){}; ChildD.prototype = new ChildC(); function ChildE(){}; ChildE.prototype = new ChildD(); function nestedFn() { var child = new ChildE(); var counter = 0; for(var i = 0; i <1000; i++) { for(var j = 0; j < 1000; j++) { for(var k = 0; k < 1000; k++) { counter += child.delta; } } } console.log('Final result: ' + counter); } nestedFn(); var end = new Date().getTime(); var diff = end - start; console.log('Total time: ' + diff + ' milliseconds');

Dans cet exemple, nous avons une longue arborescence d'héritage et trois boucles imbriquées. À l'intérieur de la boucle la plus profonde, la variable de compteur est incrémentée de la valeur delta. Mais delta est situé presque au sommet de l'arbre d'héritage! Cela signifie qu'à chaque fois child.delta est accessible, l'arborescence complète doit être parcourue de bas en haut. Cela peut avoir un impact vraiment négatif sur les performances.

Comprenant cela, nous pouvons facilement améliorer les performances de ce qui précède nestedFn en utilisant une fonction locale delta variable pour mettre en cache la valeur dans child.delta (et évitez ainsi la nécessité d'une traversée répétitive de tout l'arbre d'héritage) comme suit:

function nestedFn() { var child = new ChildE(); var counter = 0; var delta = child.delta; // cache child.delta value in current scope for(var i = 0; i <1000; i++) { for(var j = 0; j < 1000; j++) { for(var k = 0; k < 1000; k++) { counter += delta; // no inheritance tree traversal needed! } } } console.log('Final result: ' + counter); } nestedFn(); var end = new Date().getTime(); var diff = end - start; console.log('Total time: ' + diff + ' milliseconds');

Bien entendu, cette technique particulière n'est viable que dans un scénario où l'on sait que la valeur de child.delta ne changera pas pendant l’exécution des boucles for; sinon, la copie locale devra être mise à jour avec la valeur actuelle.

OK, exécutons les deux versions de nestedFn méthode et voir s'il y a une différence de performance appréciable entre les deux.

Nous allons commencer par exécuter le premier exemple d'une node.js REPL :

[email protected] :~$ node test.js Final result: 10000000000 Total time: 8270 milliseconds

Cela prend donc environ 8 secondes à fonctionner. C'est une longue période.

Voyons maintenant ce qui se passe lorsque nous exécutons la version optimisée:

[email protected] :~$ node test2.js Final result: 10000000000 Total time: 1143 milliseconds

Cette fois, cela n'a pris qu'une seconde. Plus vite!

Notez que l'utilisation de variables locales pour éviter des recherches coûteuses est une technique qui peut être appliquée à la fois pour la recherche de propriétés (via la chaîne de prototypes) et pour les recherches de variables (via la chaîne de portées).

De plus, ce type de «mise en cache» des valeurs (c'est-à-dire dans les variables de la portée locale) peut également être bénéfique lors de l'utilisation de certaines des bibliothèques JavaScript les plus courantes. Prendre jQuery , par exemple. jQuery prend en charge la notion de «sélecteurs», qui sont essentiellement un mécanisme pour récupérer un ou plusieurs éléments correspondants dans le JUGEMENT . La facilité avec laquelle on peut spécifier des sélecteurs dans jQuery peut faire oublier à quel point chaque recherche de sélecteur peut être coûteuse (du point de vue des performances). En conséquence, le stockage des résultats de recherche de sélecteur dans une variable locale peut être extrêmement bénéfique pour les performances. Par exemple:

// this does the DOM search for $('.container') 'n' times for (var i = 0; i ”); } // this accomplishes the same thing... // but only does the DOM search for $('.container') once, // although it does still modify the DOM 'n' times var $container = $('.container'); for (var i = 0; i '); } // or even better yet... // this version only does the DOM search for $('.container') once // AND only modifies the DOM once var $html = ''; for (var i = 0; i '; } $('.container').append($html);

Surtout sur une page Web avec un grand nombre d'éléments, la deuxième approche de l'exemple de code ci-dessus peut potentiellement entraîner des performances nettement meilleures que la première.

Emballer

La recherche de données en JavaScript est assez différente de celle de la plupart des autres langages, et elle est très nuancée. Il est donc essentiel de comprendre pleinement et correctement ces concepts afin de vraiment maîtriser la langue. Recherche de données et autres erreurs JavaScript courantes doit être évité autant que possible. Cette compréhension est susceptible de produire un code plus propre et plus robuste qui améliore les performances JavaScript.

En relation: En tant que développeur JS, c'est ce qui me tient éveillé la nuit / qui donne un sens à la confusion de classe ES6

Méthodes de recherche UX et voie vers l'empathie des utilisateurs

Conception Ux

Méthodes de recherche UX et voie vers l'empathie des utilisateurs
Adobe XD vs Sketch - Showdown 2020

Adobe XD vs Sketch - Showdown 2020

Conception De L'interface Utilisateur

Articles Populaires
Création d'une API REST Node.js / TypeScript, partie 2: modèles, middleware et services
Création d'une API REST Node.js / TypeScript, partie 2: modèles, middleware et services
Un didacticiel sur la radio définie par logiciel: images de la Station spatiale internationale et écoute de jambons avec un RTL-SDR
Un didacticiel sur la radio définie par logiciel: images de la Station spatiale internationale et écoute de jambons avec un RTL-SDR
Les drones commerciaux révolutionnent les opérations commerciales
Les drones commerciaux révolutionnent les opérations commerciales
Faire des affaires dans l'Union européenne
Faire des affaires dans l'Union européenne
AI vs BI: différences et synergies
AI vs BI: différences et synergies
 
Stratège de contenu produit
Stratège de contenu produit
Risque vs récompense: un guide pour comprendre les conteneurs logiciels
Risque vs récompense: un guide pour comprendre les conteneurs logiciels
Explorer SMACSS: architecture évolutive et modulaire pour CSS
Explorer SMACSS: architecture évolutive et modulaire pour CSS
Si vous n'utilisez pas de données UX, ce n'est pas de la conception UX
Si vous n'utilisez pas de données UX, ce n'est pas de la conception UX
Simplification de l'utilisation des API RESTful et de la persistance des données sur iOS avec Mantle et Realm
Simplification de l'utilisation des API RESTful et de la persistance des données sur iOS avec Mantle et Realm
Articles Populaires
  • qu'est-ce qu'un fichier nib
  • pourquoi un architecte pourrait-il choisir un rythme simple et répétitif pour son bâtiment ?
  • javascript convertir l'horodatage en date
  • comment appliquer l'apprentissage automatique
  • document de spécification pour le développement de logiciels
  • comment évaluer une option de vente
Catégories
  • Rise Of Remote
  • Outils Et Tutoriels
  • Équipes Distribuées
  • Mode De Vie
  • © 2022 | Tous Les Droits Sont Réservés

    portaldacalheta.pt