Question Quelle est la portée des variables dans JavaScript?


Quelle est la portée des variables en javascript? Ont-ils la même portée à l'intérieur plutôt qu'à l'extérieur d'une fonction? Ou est-ce important? De même, où sont stockées les variables si elles sont définies globalement?


1711
2018-02-01 08:27


origine


Réponses:


Je pense que le mieux que je puisse faire est de vous donner un tas d'exemples à étudier. Les programmeurs Javascript sont pratiquement classés en fonction de leur compréhension de la portée. Cela peut parfois être contre-intuitif.

  1. Une variable à portée globale

    // global scope
    var a = 1;
    
    function one() {
      alert(a); // alerts '1'
    }
    
  2. Portée locale

    // global scope
    var a = 1;
    
    function two(a) {
      // local scope
      alert(a); // alerts the given argument, not the global value of '1'
    }
    
    // local scope again
    function three() {
      var a = 3;
      alert(a); // alerts '3'
    }
    
  3. Intermédiaire: Rien de tel que la portée du bloc en JavaScript (ES5; ES6 introduit let)

    une.

    var a = 1;
    
    function four() {
      if (true) {
        var a = 4;
      }
    
      alert(a); // alerts '4', not the global value of '1'
    }
    

    b.

    var a = 1;
    
    function one() {
      if (true) {
        let a = 4;
      }
    
      alert(a); // alerts '1' because the 'let' keyword uses block scoping
    }
    
  4. Intermédiaire: Propriétés de l'objet

    var a = 1;
    
    function Five() {
      this.a = 5;
    }
    
    alert(new Five().a); // alerts '5'
    
  5. Avancée: Fermeture

    var a = 1;
    
    var six = (function() {
      var a = 6;
    
      return function() {
        // JavaScript "closure" means I have access to 'a' in here,
        // because it is defined in the function in which I was defined.
        alert(a); // alerts '6'
      };
    })();
    
  6. Avancée: Résolution de portée basée sur le prototype

    var a = 1;
    
    function seven() {
      this.a = 7;
    }
    
    // [object].prototype.property loses to
    // [object].property in the lookup chain. For example...
    
    // Won't get reached, because 'a' is set in the constructor above.
    seven.prototype.a = -1;
    
    // Will get reached, even though 'b' is NOT set in the constructor.
    seven.prototype.b = 8;
    
    alert(new seven().a); // alerts '7'
    alert(new seven().b); // alerts '8'
    

  7. Global + Local: Un cas très complexe

    var x = 5;
    
    (function () {
        console.log(x);
        var x = 10;
        console.log(x); 
    })();
    

    Cela va imprimer undefined et 10 plutôt que 5 et 10 puisque JavaScript déplace toujours les déclarations de variables (pas les initialisations) en haut de la portée, ce qui rend le code équivalent à:

    var x = 5;
    
    (function () {
        var x;
        console.log(x);
        x = 10;
        console.log(x); 
    })();
    
  8. Variable portée par la clause Catch

    var e = 5;
    console.log(e);
    try {
        throw 6;
    } catch (e) {
        console.log(e);
    }
    console.log(e);
    

    Cela va imprimer 5, 6, 5. À l'intérieur de la clause de capture e ombre les variables globales et locales. Mais cette portée spéciale est seulement pour la variable capturée. Si vous écrivez var f; à l'intérieur de la clause catch, alors c'est exactement la même chose que si vous l'aviez définie avant ou après le bloc try-catch.


2262
2018-02-01 08:58



Javascript utilise des chaînes de portée pour établir la portée d'une fonction donnée. Il existe généralement une portée globale et chaque fonction définie possède sa propre portée imbriquée. Toute fonction définie dans une autre fonction a une portée locale qui est liée à la fonction externe. C'est toujours la position dans la source qui définit la portée.

Un élément de la chaîne de portée est essentiellement une carte avec un pointeur vers sa portée parente.

Lors de la résolution d'une variable, javascript commence à la portée la plus interne et effectue une recherche vers l'extérieur.


219
2018-02-01 08:35



Les variables déclarées globalement ont une portée globale. Les variables déclarées dans une fonction sont limitées à cette fonction et les variables globales d'ombre du même nom.

(Je suis sûr qu'il y a beaucoup de subtilités que les vrais programmeurs JavaScript pourront mettre en évidence dans d'autres réponses. cette page à propos de quoi exactement this signifie à tout moment. Espérons ce lien plus introductif est suffisant pour vous aider à démarrer si.)


93
2018-02-01 08:31



Vieille école JavaScript

Traditionnellement, JavaScript n'a vraiment que deux types de portée:

  1. Portée mondiale : Les variables sont connues dans toute l'application, depuis le début de l'application (*)
  2. Portée fonctionnelle : Les variables sont connues dans la fonction ils sont déclarés depuis le début de la fonction (*)

Je ne vais pas m'étendre là-dessus, car il y a déjà beaucoup d'autres réponses expliquant la différence.


JavaScript moderne

le les plus récentes spécifications JavaScript maintenant aussi permettre une troisième portée:

  1. Bloquer la portée : Les variables sont connues dans le blocils sont déclarés, à partir du moment où ils sont déclarés (**)

Comment créer des variables de portée de bloc?

Traditionnellement, vous créez vos variables comme ceci:

var myVariable = "Some text";

Les variables d'étendue de bloc sont créées comme ceci:

let myVariable = "Some text";

Alors, quelle est la différence entre la portée fonctionnelle et la portée du bloc?

Pour comprendre la différence entre la portée fonctionnelle et la portée de bloc, considérez le code suivant:

// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here

function loop(arr) {
    // i IS known here, but undefined
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( var i = 0; i < arr.length; i++ ) {
        // i IS known here, and has a value
        // j IS NOT known here
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( let j = 0; j < arr.length; j++ ) {
        // i IS known here, and has a value
        // j IS known here, and has a value
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here
}

loop([1,2,3,4]);

for( var k = 0; k < arr.length; k++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS NOT known here
};

for( let l = 0; l < arr.length; l++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS known here, and has a value
};

loop([1,2,3,4]);

// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here

Ici, nous pouvons voir que notre variable j est seulement connu dans la première boucle, mais pas avant et après. Pourtant, notre variable i est connu dans toute la fonction.

En outre, considérez que les variables de portée de bloc ne sont pas connues avant d'être déclarées car elles ne sont pas levées. Vous n'êtes également pas autorisé à redéclarer la même variable étendue de bloc dans le même bloc. Cela rend les variables à portée de bloc moins sujettes aux erreurs que les variables à portée globale ou fonctionnelle, qui sont hissées et qui ne produisent pas d'erreurs dans le cas de déclarations multiples.


Est-il sûr d'utiliser des variables de portée de bloc aujourd'hui?

Qu'il soit ou non sûr à utiliser aujourd'hui, dépend de votre environnement:

  • Si vous écrivez du code JavaScript côté serveur (Node.js), vous pouvez utiliser le let déclaration.

  • Si vous écrivez du code JavaScript côté client et utilisez un transpiler (comme Traceur), vous pouvez utiliser le let déclaration, cependant votre code est susceptible d'être tout sauf optimal en ce qui concerne la performance.

  • Si vous écrivez du code JavaScript côté client et n'utilisez pas de transpiler, vous devez prendre en compte le support du navigateur.

    Aujourd'hui, 23 Février 2016, ce sont des navigateurs qui ne supportent pas let ou ont seulement un support partiel:

    • Internet Explorer 10 et ci-dessous (pas de support)
    • Firefox 43 et ci-dessous (pas de support)
    • Safari 9 et ci-dessous (pas de support)
    • Opera Mini 8 et ci-dessous (pas de support)
    • Navigateur Android 4 et ci-dessous (pas de support)
    • Opera 36 et ci-dessous (support partiel)
    • Chome 51 et ci-dessous (support partiel)

enter image description here


Comment suivre le support du navigateur

Pour un aperçu à jour des navigateurs supportant le let déclaration au moment de votre lecture de cette réponse, voir ce Can I Use page.


(*) Les variables globales et fonctionnelles peuvent être initialisées et utilisées avant d'être déclarées car les variables JavaScript sont hissé. Cela signifie que les déclarations sont toujours très en haut de la portée.

(**) Les variables de portée de bloc ne sont pas hissées


55
2018-02-23 18:51



Voici un exemple:

<script>

var globalVariable = 7; //==window.globalVariable

function aGlobal( param ) { //==window.aGlobal(); 
                            //param is only accessible in this function
  var scopedToFunction = {
    //can't be accessed outside of this function

    nested : 3 //accessible by: scopedToFunction.nested
  };

  anotherGlobal = {
    //global because there's no `var`
  }; 

}

</script>

Vous aurez envie d'enquêter sur les fermetures, et comment les utiliser pour faire membres privés.


35
2018-02-01 08:48



La clef, si je comprends bien, est que Javascript a une portée de niveau de fonction contre le scoping plus commun de bloc de C.

Voici un bon article sur le sujet.


28
2018-05-15 17:38



Dans "Javascript 1.7" (extension de Mozilla à Javascript) on peut aussi déclarer des variables de bloc-portée avec let déclaration:

 var a = 4;
 let (a = 3) {
   alert(a); // 3
 }
 alert(a);   // 4

23
2018-04-06 11:19



L'idée de scoping en JavaScript à l'origine conçu par Brendan Eich vient du HyperCard langage de script HyperTalk.

Dans cette langue, les affichages étaient similaires à une pile de fiches. Il y avait une carte maîtresse appelée arrière-plan. C'était transparent et peut être vu comme la carte du bas. Tout le contenu de cette carte de base a été partagé avec des cartes placées dessus. Chaque carte placée sur le dessus avait son propre contenu qui avait la priorité sur la carte précédente, mais avait toujours accès aux cartes précédentes si désiré.

C'est exactement ainsi que le système de portée JavaScript est conçu. Il a juste des noms différents. Les cartes en JavaScript sont connues comme Contextes d'exécutionECMA. Chacun de ces contextes contient trois parties principales. Un environnement variable, un environnement lexical et une liaison de ce type. Pour en revenir à la référence des cartes, l'environnement lexical contient tout le contenu des cartes précédentes plus bas dans la pile. Le contexte actuel est en haut de la pile et tout contenu déclaré sera stocké dans l'environnement variable. L'environnement variable aura préséance dans le cas de collisions de nommage.

Cette liaison pointe vers l'objet conteneur. Parfois, les étendues ou les contextes d'exécution changent sans que l'objet conteneur ne change, comme dans une fonction déclarée où l'objet conteneur peut être window ou une fonction constructeur.

Ces contextes d'exécution sont créés à tout moment où le contrôle est transféré. Le contrôle est transféré lorsque le code commence à s'exécuter, principalement à partir de l'exécution de la fonction.

Donc, c'est l'explication technique. En pratique, il est important de se souvenir qu'en JavaScript

  • Les portées sont techniquement des "Contextes d'exécution"
  • Les contextes forment une pile d'environnements dans lesquels les variables sont stockées
  • Le haut de la pile a la priorité (le bas étant le contexte global)
  • Chaque fonction crée un contexte d'exécution (mais pas toujours une nouvelle cette liaison)

En appliquant ceci à l'un des exemples précédents (5. "Fermeture") sur cette page, il est possible de suivre la pile de contextes d'exécution. Dans cet exemple, il y a trois contextes dans la pile. Ils sont définis par le contexte externe, le contexte dans la fonction invoquée immédiatement par var six, et le contexte dans la fonction retournée à l'intérieur de la fonction invoquée immédiatement par var six.

jeLe contexte externe. Il a un environnement variable de a = 1
iiLe contexte IIFE, il a un environnement lexical de a = 1, mais un environnement variable de a = 6 qui a la priorité dans la pile
iii) Le contexte de la fonction retournée, il a un environnement lexical de a = 6 et c'est la valeur référencée dans l'alerte lorsqu'il est appelé.

enter image description here


18
2017-09-14 20:29



1) Il existe une portée globale, une portée de fonction et les étendues with et catch. Il n'y a pas de portée de niveau 'bloc' en général pour les variables - les instructions with et catch ajoutent des noms à leurs blocs.

2) Les étendues sont imbriquées par des fonctions jusqu'à la portée globale.

3) Les propriétés sont résolues en passant par la chaîne prototype. L'instruction with apporte les noms de propriété d'objet dans la portée lexicale définie par le bloc with.

EDIT: ECMAAScript 6 (Harmony) est spec'ed pour soutenir let, et je sais que le chrome permet un drapeau d'harmonie, alors peut-être que cela le supporte.

Supposons que vous utilisiez le mot-clé au niveau du bloc, mais vous devez utiliser le mot-clé pour y arriver.

EDIT: Sur la base des remarques de Benjamin sur les déclarations with et catch dans les commentaires, j'ai modifié le post, et ajouté plus. Les instructions with et catch introduisent des variables dans leurs blocs respectifs, et est une portée de bloc. Ces variables sont aliasées aux propriétés des objets qui leur sont transmis.

 //chrome (v8)

 var a = { 'test1':'test1val' }
 test1   // error not defined
 with (a) { var test1 = 'replaced' }
 test1   // undefined
 a       // a.test1 = 'replaced'

EDIT: Exemple de clarification:

test1 est étendu au bloc with, mais est aliasé par a.test1. 'Var test1' crée une nouvelle variable test1 dans le contexte lexical supérieur (function, ou global), sauf s'il s'agit d'une propriété de - ce qu'elle est.

Yikes! Soyez prudent en utilisant 'avec' - tout comme var est un noop si la variable est déjà définie dans la fonction, c'est aussi un noop par rapport aux noms importés de l'objet! Un peu de tête sur le nom déjà défini rendrait cela beaucoup plus sûr. Personnellement, je ne vais jamais utiliser à cause de cela.


16
2017-10-25 00:41