web-dev-qa-db-fra.com

Surpris que la variable globale ait une valeur indéfinie en JavaScript

Aujourd'hui, j'ai été complètement surpris quand j'ai vu qu'une variable globale avait undefined valeur dans certains cas.

Exemple:

var value = 10;
function test() {
    //A
    console.log(value);
    var value = 20;

    //B
    console.log(value);
}
test();

Donne la sortie comme

undefined
20

Ici, pourquoi le moteur JavaScript considère-t-il la valeur globale comme undefined? Je sais que JavaScript est un langage interprété. Comment est-il capable de prendre en compte des variables dans la fonction?

Est-ce un piège du moteur JavaScript?

79
iamjustcoder

Ce phénomène s'appelle: JavaScript Variable Hoisting .

Vous n’accédez à aucun moment à la variable globale de votre fonction; vous n’accédez qu’à la variable locale value.

Votre code est équivalent à celui-ci:

var value = 10;

function test() {
    var value;
    console.log(value);

    value = 20;
    console.log(value);
}

test();

Vous êtes toujours surpris d'obtenir undefined?


Explication:

C’est quelque chose que tout programmeur JavaScript rencontre tôt ou tard. En termes simples, quelles que soient les variables que vous déclarez sont toujours levées en haut de votre fermeture locale. Ainsi, même si vous avez déclaré votre variable après le premier appel console.log, Il est toujours considéré comme si vous l'aviez déclarée auparavant.
Cependant, seule la partie déclaration est en cours de levage; l'assignation, d'autre part, n'est pas.

Ainsi, lorsque vous avez appelé pour la première fois console.log(value), vous faisiez référence à votre variable déclarée localement, à laquelle rien n'a encore été affecté. d'où undefined.

Voici n autre exemple :

var test = 'start';

function end() {
    test = 'end';
    var test = 'local';
}

end();
alert(test);

Que pensez-vous que cela va alerter? Non, ne vous contentez pas de lire, réfléchissez-y. Quelle est la valeur de test?

Si vous avez dit autre chose que start, vous vous êtes trompé. Le code ci-dessus est équivalent à ceci:

var test = 'start';

function end() {
    var test;
    test = 'end';
    test = 'local';
}

end();
alert(test);

de sorte que la variable globale n'est jamais affectée.

Comme vous pouvez le constater, peu importe où vous avez placé votre déclaration de variable, elle est toujours levée en haut de votre fermeture locale.


Note de côté:

Ceci s'applique également aux fonctions.

Considérez ce morceau de code :

test("Won't work!");

test = function(text) { alert(text); }

ce qui vous donnera une erreur de référence:

Uncaught ReferenceError: le test n'est pas défini

Cela jette beaucoup de développeurs, puisque ce morceau de code fonctionne bien:

test("Works!");

function test(text) { alert(text); }

Comme indiqué, la raison en est que la partie affectation est et non levée. Ainsi, dans le premier exemple, lorsque test("Won't work!") a été exécuté, la variable test a déjà été déclarée, mais la fonction ne lui a pas encore été affectée.

Dans le deuxième exemple, nous n'utilisons pas d'affectation de variable. Nous utilisons plutôt la syntaxe de déclaration de fonction appropriée, qui fait obtient la fonction complètement hissée.


Ben Cherry a écrit un excellent article à ce sujet, intitulé de manière appropriée JavaScript Scoping and Hoisting .
Lis le. Cela vous donnera une image complète et détaillée.

164
Joseph Silber

J'étais un peu déçu que le problème ici soit expliqué, mais personne n'a proposé de solution. Si vous souhaitez accéder à une variable globale dans la portée de la fonction sans que la fonction crée d'abord une variable locale non définie, référencez-la comme suit: window.varName

48
Amalgovinus

Les variables en JavaScript ont toujours la portée de l'ensemble des fonctions. Même s'ils ont été définis au milieu de la fonction, ils sont visibles auparavant. Des phénomènes similaires peuvent être observés avec le levage de fonction.

Cela étant dit, la première console.log(value) voit la variable value (la variable interne qui ombrage le value extérieur), mais elle n'a pas encore été initialisée. Vous pouvez le voir comme si toutes les déclarations de variables étaient implicitement déplacées au début de la fonction ( et non le bloc de code le plus interne), alors que les définitions sont laissé au même endroit.

Voir également

10
Tomasz Nurkiewicz

Il y a une variable globale value, mais lorsque le contrôle entre dans la fonction test, une autre variable value est déclarée, qui masque la variable globale. Depuis les déclarations de variable (mais pas les assignations) en JavaScript sont hissées au sommet de la portée dans laquelle elles sont déclarées:

//value == undefined (global)
var value = 10;
//value == 10 (global)

function test() {
    //value == undefined (local)
    var value = 20;
    //value == 20 (local)
}
//value == 10 (global)

Notez qu'il en va de même pour les déclarations de fonction, ce qui signifie que vous pouvez appeler une fonction avant qu'elle ne soit définie dans votre code:

test(); //Call the function before it appears in the source
function test() {
    //Do stuff
}

Il est également intéressant de noter que lorsque vous combinez les deux dans une expression de fonction, la variable sera undefined jusqu'à l'attribution, de sorte que vous ne pouvez pas appeler la fonction tant que cela ne se produit pas:

var test = function() {
    //Do stuff
};
test(); //Have to call the function after the assignment
3
James Allardice
  1. Le moyen le plus simple de conserver l’accès aux variables externes (et pas seulement de portée globale) est bien sûr d’essayer de ne pas les déclarer à nouveau sous le même nom dans les fonctions; il suffit de ne pas utiliser var ici. L'utilisation de règles de nommage appropriées est conseillée. Avec ceux-ci, il sera difficile de se retrouver avec des variables nommées comme value (cet aspect n'est pas nécessairement lié à l'exemple de la question car ce nom de variable aurait pu être donné pour la simplicité).

  2. Si la fonction peut être réutilisée ailleurs et qu'il n'y a donc aucune garantie que la variable externe définie dans ce nouveau contexte, la fonction Eval peut être utilisée. Il est lent dans cette opération, il n'est donc pas recommandé pour les fonctions exigeantes en performances:

    if (typeof variable === "undefined")
    {
        eval("var variable = 'Some value';");
    }
    
  3. Si la variable de portée externe à laquelle vous souhaitez accéder est définie dans une fonction nommée, elle peut être attachée à la fonction elle-même en premier lieu, puis accessible de n'importe où dans le code - que ce soit à partir de fonctions profondément imbriquées ou de gestionnaires d'événements en dehors de tout le reste. Notez que l’accès aux propriétés est beaucoup plus lent et qu’il vous faudrait changer votre façon de programmer. Il est donc déconseillé de le faire sauf si cela est vraiment nécessaire: Variables en tant que propriétés de fonctions (JSFiddle) :

    // (the wrapper-binder is only necessary for using variables-properties
    // via "this"instead of the function's name)
    var functionAsImplicitObjectBody = function()
    {
        function someNestedFunction()
        {
            var redefinableVariable = "redefinableVariable's value from someNestedFunction";
            console.log('--> functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
            console.log('--> redefinableVariable: ', redefinableVariable);
        }
        var redefinableVariable = "redefinableVariable's value from someFunctionBody";
        console.log('this.variableAsProperty: ', this.variableAsProperty);
        console.log('functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
        console.log('redefinableVariable: ', redefinableVariable);
        someNestedFunction();
    },
    functionAsImplicitObject = functionAsImplicitObjectBody.bind(functionAsImplicitObjectBody);
    functionAsImplicitObjectBody.variableAsProperty = "variableAsProperty's value, set at time stamp: " + (new Date()).getTime();
    functionAsImplicitObject();
    
    // (spread-like operator "..." provides passing of any number of arguments to
    // the target internal "func" function in as many steps as necessary)
    var functionAsExplicitObject = function(...arguments)
    {
        var functionAsExplicitObjectBody = {
            variableAsProperty: "variableAsProperty's value",
            func: function(argument1, argument2)
            {
                function someNestedFunction()
                {
                    console.log('--> functionAsExplicitObjectBody.variableAsProperty: ',
                        functionAsExplicitObjectBody.variableAsProperty);
                }
                console.log("argument1: ", argument1);
                console.log("argument2: ", argument2);
                console.log("this.variableAsProperty: ", this.variableAsProperty);
                someNestedFunction();
            }    
        };
        return functionAsExplicitObjectBody.func(...arguments);
    };
    functionAsExplicitObject("argument1's value", "argument2's value");
    
0
DDRRSS