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

87

Aujourd'hui, j'ai été complètement surpris quand j'ai vu qu'une variable globale a une undefinedvaleur 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 peut-il prendre en compte les variables de la fonction?

Est-ce un piège du moteur JavaScript?

iamjustcoder
la source

Réponses:

175

Ce phénomène est connu sous le nom de: Levage variable JavaScript .

A aucun moment vous n'accédez à la variable globale de votre fonction; vous n'accédez jamais qu'à la valuevariable locale .

Votre code équivaut à ce qui suit:

var value = 10;

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

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

test();

Vous êtes toujours surpris undefined?


Explication:

C'est quelque chose que chaque programmeur JavaScript rencontre tôt ou tard. En termes simples, les variables que vous déclarez sont toujours hissées au sommet de votre fermeture locale. Ainsi, même si vous avez déclaré votre variable après le premier console.logappel, elle est toujours considérée comme si vous l'aviez déclarée avant cela.
Cependant, seule la partie déclaration est soulevée; la cession, en revanche, ne l'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 ne lui est encore assigné; par conséquent undefined.

Voici un autre exemple :

var test = 'start';

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

end();
alert(test);

Que pensez-vous que cela alertera? Non, ne vous contentez pas de lire, pensez-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);

afin que la variable globale ne soit jamais affectée.

Comme vous pouvez le voir, peu importe où vous placez votre déclaration de variable, elle est toujours hissée en haut de votre fermeture locale.


Note latérale:

Cela 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 dérange beaucoup de développeurs, car ce morceau de code fonctionne bien:

test("Works!");

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

La raison à cela, comme indiqué, est que la partie d'affectation n'est pas hissée. Ainsi, dans le premier exemple, lors de l' test("Won't work!")exécution, la testvariable a déjà été déclarée, mais la fonction ne lui est pas encore affectée.

Dans le deuxième exemple, nous n'utilisons pas l'affectation de variables. Nous utilisons plutôt une syntaxe de déclaration de fonction appropriée, ce qui permet de hisser complètement la fonction.


Ben Cherry a écrit un excellent article à ce sujet, intitulé à juste titre JavaScript Scoping and Hoisting .
Lis le. Cela vous donnera une image complète en détail.

Joseph Silber
la source
27
C'est une bonne explication. Cependant, je manque une solution où vous pouvez accéder aux variables globales à l'intérieur d'une fonction.
DuKes0mE
1
@ DuKes0mE - vous pouvez toujours accéder aux variables globales à l'intérieur d'une fonction.
Joseph Silber
3
oui, et pourquoi le cas A du message d'ouverture ne fonctionne-t-il pas et dit qu'il est indéfini? Je comprends que cela sera interprété comme une variable locale au lieu de global, mais comment cela va-t-il être global alors?
DuKes0mE
4
pour accéder à la variable globale, utilisez window.value
Venkat Reddy
1
quand ce style de levage variable a-t-il été mis en œuvre? cela a-t-il toujours été standard en javascript?
Dieskim
54

J'ai été quelque 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 ne crée d'abord une variable locale non définie, référencez la variable commewindow.varName

Amalgovinus
la source
8
Ouais, ça craint que les autres gars n'aient pas proposé de solution car c'est le premier résultat dans les résultats Google. Ils ont PRESQUE répondu à la question réelle posée sur le fait qu'il s'agissait d'un piège dans le moteur js .... soupir -> merci pour votre réponse
user1567453
2
C'est la différence entre les connaissances théoriques et le travail accompli. Merci pour cette réponse!
hansTheFranz
Pour moi, c'est une erreur que j'ai choisi un nom global n'importe où dans n'importe quelle fonction. Prend au moins de la confusion. Nécessite au pire une recherche sur Google. Merci
dcromley
Javascript ne cesse de me surprendre chaque jour qui passe. Merci mec, la réponse a été utile.
Raf
Merci pour la solution, j'ai trouvé cela après qu'une variable globale n'a pas été définie mais uniquement dans Safari. D'autres fichiers "include" et des variables globales sont apparus tels que "google", j'ai donc copié l'approche utilisée par google: window.globalVarFromJS = window.globalVarFromJS || {}; Ensuite, j'ai trouvé votre solution alors j'ai pensé y ajouter.
Ralph Hinkley
10

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

Cela étant dit, le premier console.log(value)voit la valuevariable ( la variable intérieure qui fait de l'ombre à l'extérieur value), mais elle n'a pas encore été initialisée. Vous pouvez y penser comme si toutes les déclarations de variables étaient implicitement déplacées au début de la fonction ( pas le bloc de code le plus interne), tandis que les définitions sont laissées au même endroit.

Voir également

Tomasz Nurkiewicz
la source
J'aime toujours les mots simples +1 :)
Jashwant
3

Il existe une variable globale value, mais lorsque le contrôle entre dans la testfonction, une autre valuevariable est déclarée, qui occulte la variable globale. Étant donné que les déclarations de variables ( mais pas les affectations ) 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 semble être 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 undefinedjusqu'à ce que l'affectation ait lieu, vous ne pouvez donc 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
James Allardice
la source
0
  1. Le moyen le plus simple de conserver l'accès aux variables externes (pas seulement de portée globale) est, bien sûr, d'essayer de ne pas les re-déclarer sous le même nom dans les fonctions; ne pas utiliser var ici. L'utilisation de règles de dénomination descriptives appropriées est conseillée. Avec ceux-ci, il sera difficile de se retrouver avec des variables nommées comme valeur (cet aspect n'est pas forcément lié à l'exemple de la question car ce nom de variable aurait pu être donné par simplicité).

  2. Si la fonction peut être réutilisée ailleurs et qu'il n'y a donc aucune garantie que la variable externe réellement 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 vous obligerait à changer la façon dont vous programmez, donc ce n'est pas recommandé sauf si c'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");
    
DDRRSS
la source
0

Je rencontrais le même problème même avec des variables globales. Mon problème, j'ai découvert, était que la variable globale ne persistait pas entre les fichiers html.

<script>
    window.myVar = 'foo';
    window.myVarTwo = 'bar';
</script>
<object type="text/html" data="/myDataSource.html"></object>

J'ai essayé de référencer myVar et myVarTwo dans le fichier HTML chargé, mais j'ai reçu l'erreur non définie. Longue histoire / jour court, j'ai découvert que je pouvais référencer les variables en utilisant:

<!DOCTYPE html>
<html lang="en">
    <!! other stuff here !!>
    <script>

        var myHTMLVar = this.parent.myVar

        /* other stuff here */
    </script>
</html>
Nathan Sutherland
la source