Quel est le résultat du programme suivant ?
Copiez le code comme suit :
var foo = 1 ;
barre de fonctions() {
si (!foo) {
var foo = 10 ;
}
alerte(foo);
}
bar();
Le résultat est 10 ;
Et celui-ci ?
Copiez le code comme suit :
var a = 1 ;
fonction b() {
une = 10 ;
retour;
fonction a() {}
}
b();
alerte(a);
Le résultat est 1.
Est-ce que ça vous fait peur ? Ce qui s'est passé? Cela peut paraître étrange, dangereux et déroutant, mais il s'agit également d'une fonctionnalité très utile et impressionnante du langage JavaScript. Je ne sais pas s'il existe un nom standard pour ce comportement, mais j'aime ce terme : « Levage ». Cet article donnera une explication introductive de ce mécanisme, mais commençons par comprendre un peu la portée de JavaScript.
Portée de Javascript
Pour les débutants en Javascript, l’un des domaines les plus déroutants est la portée. En fait, cela ne concerne pas uniquement les débutants. J'ai rencontré des programmeurs JavaScript expérimentés, mais ils n'en comprennent pas profondément la portée. La raison pour laquelle la portée de JavaScript prête à confusion est que la syntaxe du programme elle-même ressemble à un langage de la famille C, comme le programme C suivant :
Copiez le code comme suit :
#include <stdio.h>
int main() {
entier x = 1 ;
printf("%d, ", x); // 1
si (1) {
entier x = 2 ;
printf("%d, ",x); // 2
}
printf("%d/n", x); // 1
}
Le résultat de sortie est 1 2 1. En effet, les langages de la famille C ont une portée de bloc Lorsque le contrôle du programme entre dans un bloc, tel qu'un bloc if, les variables qui affectent uniquement le bloc peuvent être déclarées sans affecter les effets en dehors du bloc. domaine. Mais en Javascript, cela ne fonctionne pas. Jetez un œil au code ci-dessous :
Copiez le code comme suit :
variable x = 1 ;
console.log(x); // 1
si (vrai) {
variable x = 2 ;
console.log(x); // 2
}
console.log(x); // 2
Le résultat sera 1 2 2. Parce que javascript est la portée de la fonction. C’est la plus grande différence avec la famille de langages C. Le if dans ce programme ne crée pas de nouvelle portée.
Pour de nombreux programmeurs C, C++ et Java, ce n’est pas ce qu’ils attendent et accueillent. Heureusement, grâce à la flexibilité des fonctions JavaScript, il existe des moyens de contourner ce problème. Si vous devez créer une étendue temporaire, procédez comme ceci :
Copiez le code comme suit :
fonction foo() {
variable x = 1 ;
si (x) {
(fonction () {
variable x = 2 ;
// un autre code
}());
}
// x vaut toujours 1.
}
Cette méthode est flexible et peut être utilisée partout où vous souhaitez créer une étendue temporaire. Pas seulement à l’intérieur du bloc. Cependant, je vous recommande fortement de prendre le temps de comprendre le cadrage de JavaScript. C'est très utile et c'est l'une de mes fonctionnalités préférées de JavaScript. Si vous comprenez la portée, le levage variable aura plus de sens pour vous.
Déclaration, dénomination et promotion des variables
En JavaScript, il existe 4 manières de base pour les variables d'entrer dans la portée :
•1 Langage intégré : toutes les étendues ont ceci et les arguments (Note du traducteur : après les tests, les arguments ne sont pas visibles dans la portée globale) ;
•2 Paramètres formels : Les paramètres formels d'une fonction feront partie de la portée du corps de la fonction ;
•3 Déclaration de fonction : sous cette forme : function foo(){};
•4 Déclaration de variable : comme ceci : var foo;
Les déclarations de fonctions et les déclarations de variables sont toujours "montées" discrètement en haut du corps de la méthode par l'interpréteur. Cela signifie, codez comme suit :
Copiez le code comme suit :
fonction foo() {
bar();
variable x = 1 ;
}
sera en fait interprété comme :
Copiez le code comme suit :
fonction foo() {
variable x ;
bar();
x = 1 ;
}
Indépendamment du fait que le bloc dans lequel la variable est définie peut être exécuté. Les deux fonctions suivantes sont en fait la même chose :
Copiez le code comme suit :
fonction foo() {
si (faux) {
variable x = 1 ;
}
retour;
var y = 1 ;
}
fonction foo() {
var x, y ;
si (faux) {
x = 1 ;
}
retour;
y = 1 ;
}
Notez que les affectations de variables ne sont pas levées, juste des déclarations. Cependant, la déclaration de fonction est un peu différente et le corps de la fonction est également promu. Mais attention, il existe deux manières de déclarer une fonction :
Copiez le code comme suit :
fonction test() {
foo(); // TypeError "foo n'est pas une fonction"
bar(); // "cela fonctionnera !"
var foo = function () { // la variable pointe vers l'expression de la fonction
alert("cela ne fonctionnera pas!");
}
function bar() { // Fonction de déclaration de fonction nommée bar
alert("cela va fonctionner!");
}
}
test();
Dans cet exemple, seules les déclarations fonctionnelles sont hissées avec le corps de la fonction. La déclaration de foo sera levée, mais le corps de fonction vers lequel elle pointe ne sera attribué que lors de l'exécution.
Ce qui précède couvre certaines des bases du boosting, et elles ne semblent pas si déroutantes. Cependant, dans certains scénarios particuliers, un certain degré de complexité subsiste.
Ordre d'analyse variable
La chose la plus importante à garder à l’esprit est l’ordre de résolution variable. Vous vous souvenez des 4 façons dont la dénomination entre dans la portée que j'ai donnée plus tôt ? L'ordre dans lequel les variables sont analysées est l'ordre dans lequel je les ai répertoriées.
Copiez le code comme suit :
<script>
fonction a(){
}
var une;
alert(a);//Imprimer le corps de la fonction d'un
</script>
<script>
var une;
fonction a(){
}
alert(a);//Imprimer le corps de la fonction d'un
</script>
//Mais faites attention à la différence entre les deux méthodes d'écriture suivantes :
<script>
var a = 1 ;
fonction a(){
}
alert(a);//Imprimer 1
</script>
<script>
fonction a(){
}
var a = 1 ;
alert(a);//Imprimer 1
</script>
Il y a 3 exceptions ici :
1 Les arguments de nom intégrés se comportent étrangement. Il semble qu'il doive être déclaré après les paramètres formels de la fonction, mais avant la déclaration de la fonction. Cela signifie que s'il y a des arguments dans le paramètre formel, il aura la priorité sur celui intégré. C'est une très mauvaise fonctionnalité, évitez donc d'utiliser des arguments dans les paramètres formels ;
2 Définir cette variable n'importe où provoquera une erreur de syntaxe, ce qui est une bonne fonctionnalité ;
3. Si plusieurs paramètres formels portent le même nom, le dernier est prioritaire, même si sa valeur n'est pas définie lors du fonctionnement réel ;
fonction nommée
Vous pouvez donner un nom à une fonction. Si tel est le cas, il ne s'agit pas d'une déclaration de fonction et le nom de fonction spécifié (le cas échéant, comme spam ci-dessous, note du traducteur) dans la définition du corps de la fonction ne sera pas promu, mais ignoré. Voici un code pour vous aider à comprendre :
Copiez le code comme suit :
foo(); // TypeError "foo n'est pas une fonction"
barre(); // valide
baz(); // TypeError "baz n'est pas une fonction"
spam(); // ReferenceError "le spam n'est pas défini"
var foo = function () {}; // foo pointe vers une fonction anonyme
function bar() {}; // déclaration de fonction
var baz = function spam() {}; // Fonction nommée, seul baz est promu, le spam ne sera pas promu.
foo(); // valide
barre(); // valide
baz(); // valide
spam(); // ReferenceError "le spam n'est pas défini"
Comment écrire du code
Maintenant que vous comprenez la portée et le levage de variables, qu'est-ce que cela signifie pour le codage JavaScript ? Le plus important est de toujours définir vos variables avec var. Et je recommande fortement que pour un nom, il n'y ait toujours qu'une seule déclaration var dans une portée. Si vous faites cela, vous n'aurez pas de problèmes de portée et de levage de variables.
Qu'entendez-vous par spécification du langage ?
Je trouve la documentation de référence ECMAScript toujours utile. Voici ce que j'ai trouvé sur la portée et le levage variable :
Si une variable est déclarée dans une classe de corps de fonction, il s'agit de la portée de la fonction. Sinon, sa portée est globale (en tant que propriété de global). Les variables seront créées lorsque l'exécution entrera dans la portée. Les blocs ne définiront pas de nouvelles étendues, seules les déclarations de fonctions et les procédures (le traducteur pense qu'il s'agit d'une exécution de code globale) créeront de nouvelles étendues. Les variables sont initialisées à undefined lors de leur création. S'il existe une opération d'affectation dans l'instruction de déclaration de variable, l'opération d'affectation ne se produira que lors de son exécution, et non lors de sa création.
J'espère que cet article apportera un rayon de lumière aux programmeurs qui sont confus au sujet de JavaScript. Je fais également de mon mieux pour éviter de créer davantage de confusion. Si j'ai dit quelque chose de mal ou si j'ai oublié quelque chose, faites-le-moi savoir.
Supplément du traducteur
Un ami m'a rappelé le problème de promotion des fonctions nommées dans le périmètre global sous IE :
Voici comment je l'ai testé lorsque j'ai traduit l'article :
Copiez le code comme suit :
<script>
fonctiont(){
courrier indésirable();
var baz = function spam() {alert('c'est du spam')};
}
t();
</script>
Cette façon d'écrire, c'est-à-dire la promotion de fonctions nommées dans une portée non globale, a les mêmes performances sous ie et ff. Je l'ai modifiée en :
Copiez le code comme suit :
<script>
courrier indésirable();
var baz = function spam() {alert('c'est du spam')};
</script>
Le spam peut alors être exécuté sous ie, mais pas sous ff. Cela montre que différents navigateurs gèrent ce détail différemment.
Cette question m'a également amené à réfléchir à deux autres questions, 1 : Pour les variables qui ont une portée globale, il y a une différence entre var et non-var. Sans var, la variable ne sera pas promue. Par exemple, parmi les deux programmes suivants, le deuxième signalera une erreur :
Copiez le code comme suit :
<script>
alerte(a);
var a = 1 ;
</script>
Copiez le code comme suit :
<script>
alerte(a);
une = 1 ;
</script>
2 : Les variables locales créées dans eval ne seront pas promues (il n'y a aucun moyen de le faire).
Copiez le code comme suit :
<script>
var a = 1 ;
fonction t(){
alerte(a);
eval('var a = 2');
alerte(a);
}
t();
alerte(a);
</script>