L'étendue, ou l'ensemble de règles qui déterminent l'emplacement de vos variables, est l'un des concepts les plus fondamentaux de tout langage de programmation. C'est tellement fondamental, en fait, qu'il est facile d'oublier à quel point les règles peuvent être subtiles!
Comprendre exactement comment le moteur JavaScript "pense" à propos de la portée vous empêchera d'écrire les bugs courants que le levage peut causer, vous prépare à bien comprendre les fermetures et vous rapproche encore plus de ne jamais écrire des bugs. déjà encore.
… Bon, ça vous aidera à comprendre le levage et les fermetures, de toute façon.
Dans cet article, nous allons examiner:
laisser
et const
changer le jeuPlongeons dedans.
Si vous souhaitez en savoir plus sur ES6 et comment utiliser la syntaxe et les fonctionnalités pour améliorer et simplifier votre code JavaScript, pourquoi ne pas consulter ces deux cours:
Si vous avez déjà écrit une ligne de JavaScript, vous saurez que où tu définir vos variables déterminent où vous pouvez utilisation leur. Le fait que la visibilité d’une variable dépend de la structure de votre code source s’appelle lexical portée.
Il existe trois façons de créer une portée en JavaScript:
laisser
ou const
à l'intérieur d'un bloc de code. De telles déclarations ne sont visibles qu'à l'intérieur du bloc. capture
bloc. Croyez-le ou non, cela en fait Est-ce que créer une nouvelle portée!"use strict"; var mr_global = "Mr Global"; fonction foo () var mrs_local = "Mrs Local"; console.log ("Je peux voir" + mr_global + "et" + mrs_local + "."); function bar () console.log ("Je peux aussi voir" + mr_global + "et" + mrs_local + "."); foo (); // Fonctionne comme prévu try console.log ("Mais / je / ne peux pas voir" + mrs_local + "."); catch (err) console.log ("Vous venez de recevoir un" + err + "."); let foo = "toto"; const bar = "bar"; console.log ("je peux utiliser" + foo + bar + "dans son bloc ..."); try console.log ("Mais pas en dehors de ça."); catch (err) console.log ("Vous venez de recevoir un autre" + err + "."); // Lance ReferenceError! console.log ("Notez que" + err + "n'existe pas en dehors de 'catch'!")
L'extrait ci-dessus illustre les trois mécanismes de portée. Vous pouvez l'exécuter dans Node ou Firefox, mais Chrome ne joue pas bien avec laisser
, encore.
Nous allons parler de chacun d’eux dans les moindres détails. Commençons par un aperçu détaillé de la manière dont JavaScript détermine quelles variables appartiennent à quelle portée..
Lorsque vous exécutez un morceau de JavaScript, deux choses se passent pour le faire fonctionner.
Pendant la compilation étape, le moteur JavaScript:
C'est seulement pendant exécution que le moteur JavaScript définit en réalité la valeur des références de variable égale à leurs valeurs d’assignation. Jusque là, ils sont indéfini
.
// Je peux utiliser first_name n'importe où dans ce programme var first_name = "Peleke"; function popup (first_name) // Je ne peux utiliser que last_name à l'intérieur de cette fonction var last_name = "Sengstacke"; alerte (prénom + "+ nom +); popup (prénom);
Passons en revue ce que fait le compilateur.
Tout d'abord, il lit la ligne var first_name = "Peleke"
. Ensuite, il détermine quoi portée enregistrer la variable dans. Parce que nous sommes au plus haut niveau du script, il se rend compte que nous sommes dans le portée globale. Ensuite, il enregistre la variable Prénom
au niveau global et initialise sa valeur à indéfini
.
Deuxièmement, le compilateur lit la ligne avec fonction popup (prenom)
. Parce que le une fonction mot-clé est la première chose sur la ligne, il crée une nouvelle portée pour la fonction, enregistre la définition de la fonction dans la portée globale, et jette un coup d'œil à l'intérieur pour trouver des déclarations de variable.
Effectivement, le compilateur en trouve un. Depuis que nous avons var last_name = "Sengstacke"
dans la première ligne de notre fonction, le compilateur enregistre la variable nom de famille
au champ d'application apparaitre
-ne pas à la portée globale - et définit sa valeur à indéfini
.
Comme il n'y a plus de déclaration de variable dans la fonction, le compilateur revient dans la portée globale. Et puisqu'il n'y a plus de déclarations de variables Là, cette phase est terminée.
Notez que nous n'avons pas réellement courir rien encore. À ce stade, le travail du compilateur consiste simplement à s'assurer qu'il connaît le nom de tout le monde. il s'en fiche quoi ils font.
À ce stade, notre programme sait que:
Prénom
dans le cadre global.apparaitre
dans le cadre global.nom de famille
dans le viseur de apparaitre
.Prénom
et nom de famille
sont indéfini
.Peu importe que nous ayons affecté ces valeurs de variables ailleurs dans notre code. Le moteur prend soin de cela dans exécution.
Au cours de l'étape suivante, le moteur lit à nouveau notre code, mais cette fois-ci, exécute il.
Tout d'abord, il lit la ligne, var first_name = "Peleke"
. Pour ce faire, le moteur recherche la variable appelée Prénom
. Étant donné que le compilateur a déjà enregistré une variable portant ce nom, le moteur la recherche et définit sa valeur sur "Peleke"
.
Ensuite, il lit la ligne, fonction popup (prenom)
. Puisque nous ne sommes pas l'exécution la fonction ici, le moteur n'est pas intéressé et saute dessus.
Enfin, il lit la ligne popup (prenom)
. Depuis que nous sont en exécutant une fonction ici, le moteur:
apparaitre
Prénom
apparaitre
en tant que fonction, en passant la valeur de Prénom
en paramètreQuand il s'exécute apparaitre
, il passe par ce même processus, mais cette fois à l'intérieur de la fonction apparaitre
. Il:
nom de famille
nom de famille
La valeur de est égale à "Sengstacke"
alerte
, l'exécuter comme une fonction avec "Peleke Sengstacke"
comme paramètreIl se trouve qu'il y a beaucoup plus de choses sous le capot qu'on aurait pu le penser!
Maintenant que vous comprenez comment JavaScript lit et exécute le code que vous écrivez, nous sommes prêts à aborder un problème un peu plus près de chez vous: comment fonctionne le levage.
Commençons avec du code.
bar(); function bar () if (! foo) alert (foo + "? C'est étrange…"); var foo = "bar"; cassé(); // Erreur-type! var cassé = function () alert ("Cette alerte ne s'affichera pas!");
Si vous exécutez ce code, vous remarquerez trois choses:
foo
avant de lui attribuer, mais sa valeur est indéfini
.cassé
avant de le définir, mais vous aurez un Erreur-type
.bar
avant de le définir, et cela fonctionne comme vous le souhaitez.Levage fait référence au fait que JavaScript rend disponibles tous nos noms de variables déclarés partout dans leurs domaines, y compris avant nous leur assignons.
Les trois cas dans l'extrait sont les trois que vous devez connaître dans votre propre code. Nous allons donc les examiner un par un..
Rappelez-vous, quand le compilateur JavaScript lit une ligne comme var foo = "bar"
, il:
foo
à la portée la plus prochefoo
à indéfiniLa raison pour laquelle nous pouvons utiliser foo
avant que nous lui attribuions est parce que, lorsque le moteur recherche la variable avec ce nom, il Est-ce que exister. C'est pourquoi il ne jette pas un ReferenceError
.
Au lieu de cela, il obtient la valeur indéfini
, et essaie de l'utiliser pour faire tout ce que vous lui avez demandé. Habituellement, c'est un bug.
Gardant cela à l’esprit, nous pourrions imaginer que ce que JavaScript voit dans notre fonction bar
est plus comme ça:
fonction bar () var foo; // undefined if (! foo) //! undefined est vrai, alors alerte alert (foo + "? C'est étrange…"); foo = "bar";
C'est le Première règle de levage, si vous voulez: des variables sont disponibles tout au long de leur portée, mais avoir la valeur indéfini
jusqu'à ce que votre code leur assigne.
Un langage JavaScript courant consiste à écrire l’ensemble de vos var
Déclarations en haut de leur portée, au lieu de leur première utilisation. Pour paraphraser Doug Crockford, cela aide votre code lis plus comme ça court.
Quand vous y réfléchissez, cela a du sens. C'est assez clair pourquoi bar
se comporte comme il le fait lorsque nous écrivons notre code comme JavaScript le lit, n'est-ce pas? Alors pourquoi ne pas simplement écrire comme ça tout le temps?
Le fait que nous ayons un Erreur-type
quand nous avons essayé d'exécuter cassé
avant que nous définissions il est juste un cas spécial de la première règle de Hoisting.
Nous avons défini une variable, appelée cassé
, que le compilateur enregistre dans la portée globale et définit égal à indéfini
. Lorsque nous essayons de l'exécuter, le moteur recherche la valeur de cassé
, trouve que c'est indéfini
, et tente d'exécuter indéfini
en tant que fonction.
Évidemment, indéfini
n'est pas une fonction-c'est pourquoi nous obtenons un Erreur-type
!
Enfin, rappelons que nous avons pu appeler bar
avant de le définir. Ceci est dû à la Deuxième règle de levage: Lorsque le compilateur JavaScript trouve une déclaration de fonction, il donne son nom et définition disponible en haut de son champ. Réécrire notre code encore une fois:
function bar () if (! foo) alert (foo + "? C'est étrange…"); var foo = "bar"; var cassé; // barre indéfinie (); // bar est déjà défini, exécute fine broken (); // Impossible d'exécuter undefined! broken = function () alert ("Cette alerte ne s'affichera pas!");
Encore une fois, cela a beaucoup plus de sens quand vous écrire comme JavaScript lit, tu ne crois pas?
Réviser:
indéfini
jusqu'à la cession.Voyons maintenant deux nouveaux outils qui fonctionnent un peu différemment: laisser
et const
.
laisser
, const
, et la zone morte temporelle contrairement à var
déclarations, variables déclarées avec laisser
et const
ne pas se faire hisser par le compilateur.
Du moins pas exactement.
Rappelez-vous comment nous avons pu appeler cassé
, mais a obtenu Erreur-type
parce que nous avons essayé d'exécuter indéfini
? Si nous avions défini cassé
avec laisser
, nous aurions eu un ReferenceError
, au lieu:
"use strict"; // Vous devez "utiliser strict" pour essayer ceci dans Node broken (); // ReferenceError! let broken = function () alert ("Cette alerte ne s'affichera pas!");
Lorsque le compilateur JavaScript enregistre les variables à leurs portées lors de sa première passe, il traite laisser
et const
différemment que ce qu'il fait var
.
Quand il trouve un var
déclaration, nous enregistrons le nom de la variable dans sa portée et initialisons immédiatement sa valeur à indéfini
.
Avec laisser
, cependant, le compilateur Est-ce que enregistrer la variable à sa portée, mais ne fait pasinitialiser sa valeur à indéfini
. Au lieu de cela, il laisse la variable non initialisée, jusqu'à ce que le moteur exécute votre instruction d'affectation. L'accès à la valeur d'une variable non initialisée jette un ReferenceError
, ce qui explique pourquoi l'extrait ci-dessus jette lorsque nous l'exécutons.
L'espace entre le début de haut de la portée d'un laisser
déclaration et la déclaration d'affectation est appelée la Zone morte temporelle. Le nom vient du fait que, même si le moteur sait à propos d'une variable appelée foo
au sommet de la portée de bar
, la variable est "morte", car elle n'a pas de valeur.
… Aussi parce que ça va tuer votre programme si vous essayez de l'utiliser tôt.
le const
mot clé fonctionne de la même manière que laisser
, avec deux différences principales:
const
.const
.Cela garantit que const
volonté toujoursavoir la valeur que vous lui avez initialement attribuée.
// C'est légal const React = require ('react'); // Ceci n'est absolument pas légal. crypto = require ('crypto');
laisser
et const
sont différents de var
d'une autre manière: la taille de leurs portées.
Lorsque vous déclarez une variable avec var
, c'est visible aussi haut que possible dans la chaîne des portées - généralement, en haut de la déclaration de fonction la plus proche, ou dans la portée globale, si vous le déclarez au niveau supérieur.
Lorsque vous déclarez une variable avec laisser
ou const
, cependant, il est visible en tant que localement comme possible-seulement dans le bloc le plus proche.
UNE bloc est une section de code définie par des accolades, comme vous le voyez avec si
/autre
des blocs, pour
des boucles et des morceaux de code explicitement "bloqués", comme dans cet extrait.
"use strict"; let foo = "foo"; if (foo) const bar = "bar"; var foobar = foo + bar; console.log ("Je peux voir" + barre + "dans ce bloc."); try console.log ("Je peux voir" + foo + "dans ce bloc, mais pas" + bar + "."); catch (err) console.log ("Vous avez un" + err + "."); essayer console.log (foo + bar); // Lance à cause de 'foo', mais les deux ne sont pas définis catch (err) console.log ("Vous venez de recevoir un" + err + "."); console.log (foobar); // fonctionne bien
Si vous déclarez une variable avec const
ou laisser
à l'intérieur d'un bloc, c'est seulement visible à l'intérieur du bloc, et seulement après l'avoir assigné.
Une variable déclarée avec var
, cependant, est visible aussi loin que possible-dans ce cas, dans la portée globale.
Si vous êtes intéressé par les détails de Nitty Gritty de laisser
et const
, Découvrez ce que le Dr Rauschmayer a à dire à leur sujet dans Exploring ES6: variables et périmètre, et consultez la documentation MDN qui s'y trouve..
ce
Fonctions de flècheÀ la surface, ce
ne semble pas avoir grand chose à voir avec la portée. Et, en fait, JavaScript ne ne pas résoudre le sens de ce
selon les règles de portée dont nous avons parlé ici.
Du moins pas d'habitude. JavaScript, notoirement, ne ne pas résoudre le sens de la ce
mot clé en fonction de l'endroit où vous l'avez utilisé:
var foo = nom: 'Foo', langues: ['espagnol', 'français', 'italien'], parle: fonction parle () this.languages.forEach (fonction (langue) console.log (this. nom + "parle" + langue + ".");); foo.speak ();
La plupart d'entre nous s'attendions à ce
vouloir dire foo
à l'intérieur de pour chaque
boucle, parce que c'est ce que cela voulait dire juste en dehors de cela. En d'autres termes, nous nous attendons à ce que JavaScript résout le sens de ce
lexicalement.
Mais ce n'est pas.
Au lieu de cela, il crée un Nouveau ce
dans chaque fonction que vous définissez, et décide de ce que cela signifie en fonction de Comment vous appelez la fonction-not où vous l'avez défini.
Ce premier point est similaire au cas de la redéfinition tout variable dans une portée enfant:
fonction foo () var bar = "bar"; function baz () // La réutilisation de noms de variable comme ceci s'appelle "shadowing" var bar = "BAR"; console.log (barre); // BAR baz (); foo (); // BAR
Remplacer bar
avec ce
, et le tout devrait éclaircir instantanément!
Traditionnellement, obtenir ce
pour fonctionner comme nous nous attendons à ce que de vieilles variables simples à portée lexicale fonctionnent, vous devez utiliser l'une des deux solutions suivantes:
var foo = name: 'Foo', langues: ['espagnol', 'français', 'italien'], speak_self: function speak_s () var self = this; self.languages.forEach (fonction (langue) console.log (self.name + "parle" + langue + ".");), talk_bound: fonction speak_b () this.languages.forEach (fonction ) console.log (this.name + "parle" + langue + "."); .bind (foo)); // Plus communément: .bind (this); ;
Dans se parler
, nous sauvons le sens de ce
à la variable soi
, et utilise cette variable pour obtenir la référence que nous voulons. Dans speak_bound
, nous utilisons lier
à en permanence point ce
à un objet donné.
ES2015 nous apporte une nouvelle alternative: les fonctions de flèche.
Contrairement aux fonctions "normales", les fonctions de flèche font ne pas l'ombre de la portée de leurs parents ce
valeur en définissant leur propre. Plutôt, ils résolvent sa signification lexicalement.
En d'autres termes, si vous utilisez ce
dans une fonction de flèche, JavaScript recherche sa valeur comme n'importe quelle autre variable.
Tout d'abord, il vérifie l'étendue locale d'un ce
valeur. Puisque les fonctions de flèche n'en définissent pas un, il n'en trouvera pas. Ensuite, il vérifie la parent la possibilité d'un ce
valeur. S'il en trouve un, il l'utilisera à la place.
Cela nous permet de réécrire le code ci-dessus comme ceci:
var foo = nom: 'Foo', langues: ['espagnol', 'français', 'italien'], parle: fonction speak () this.languages.forEach ((language) => console.log (this .name + "parle" + langue + "."););
Si vous souhaitez plus de détails sur les fonctions de flèche, jetez un coup d'œil à l'excellent cours du formateur Dan Wellman sur les bases de JavaScript ES6 en JavaScript, ainsi qu'à la documentation MDN sur les fonctions de flèche..
Nous avons couvert beaucoup de terrain jusqu'à présent! Dans cet article, vous avez appris que:
laisser
ou const
avant l'affectation jette un ReferenceError
, et que ces variables sont étendues au bloc le plus proche.ce
, et contourner la liaison dynamique traditionnelle.Vous avez également vu les deux règles du levage:
var
les déclarations sont disponibles dans toutes les portées où elles sont définies, mais ont la valeur indéfini
jusqu'à ce que vos instructions d'affectation soient exécutées.Une bonne étape suivante consiste à utiliser votre connaissance nouvelle des domaines de JavaScript pour vous envelopper des fermetures. Pour cela, consultez Scopes & Closures de Kyle Simpson.
Enfin, il y a beaucoup plus à dire sur ce
que j'ai pu couvrir ici. Si le mot-clé vous semble toujours être une magie noire, jetez un coup d'œil à ceci et aux prototypes d'objet pour mieux vous comprendre..
En attendant, prenez ce que vous avez appris et écrivez moins de bugs!
Apprendre JavaScript: le guide complet
Nous avons créé un guide complet pour vous aider à apprendre le JavaScript, que vous soyez débutant en tant que développeur Web ou que vous souhaitiez explorer des sujets plus avancés..