Surpreso que a variável global tem valor indefinido em JavaScript

87

Hoje, fiquei completamente surpreso quando vi que uma variável global tem undefinedvalor em determinado caso.

Exemplo:

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

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

Dá saída como

undefined
20

Aqui, por que o mecanismo JavaScript está considerando o valor global como undefined. Eu sei que JavaScript é uma linguagem interpretada. Como ele consegue considerar variáveis ​​na função?

Isso é uma armadilha do mecanismo JavaScript?

iamjustcoder
fonte

Respostas:

175

Esse fenômeno é conhecido como: JavaScript Variable Hoisting .

Em nenhum momento você acessa a variável global em sua função; você está sempre acessando a valuevariável local .

Seu código é equivalente ao seguinte:

var value = 10;

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

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

test();

Ainda está surpreso com o que você está recebendo undefined?


Explicação:

Isso é algo que todo programador de JavaScript se depara mais cedo ou mais tarde. Simplificando, quaisquer variáveis ​​que você declarar são sempre içadas para o topo de seu fechamento local. Portanto, mesmo que você tenha declarado sua variável após a primeira console.logchamada, ela ainda é considerada como se você a tivesse declarado antes disso.
No entanto, apenas a parte da declaração está sendo içada; a atribuição, por outro lado, não é.

Portanto, quando você chamou pela primeira vez console.log(value), estava referenciando sua variável declarada localmente, que ainda não tem nada atribuído a ela; daí undefined.

Aqui está outro exemplo :

var test = 'start';

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

end();
alert(test);

O que você acha que isso alertará? Não, não apenas continue lendo, pense sobre isso. Qual é o valor de test?

Se você disse qualquer coisa diferente start, você estava errado. O código acima é equivalente a este:

var test = 'start';

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

end();
alert(test);

para que a variável global nunca seja afetada.

Como você pode ver, não importa onde você coloque sua declaração de variável, ela sempre é içada para o topo de seu fechamento local.


Nota:

Isso também se aplica a funções.

Considere esta parte do código :

test("Won't work!");

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

o que dará a você um erro de referência:

ReferenceError não capturado: teste não definido

Isso confunde muitos desenvolvedores, pois este código funciona bem:

test("Works!");

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

A razão para isso, conforme declarado, é porque a peça de atribuição não é içada. Portanto, no primeiro exemplo, quando test("Won't work!")foi executado, a testvariável já foi declarada, mas ainda não teve a função atribuída a ela.

No segundo exemplo, não estamos usando atribuição de variável. Em vez disso, estamos usando a sintaxe de declaração de função apropriada, o que faz com que a função seja completamente içada.


Ben Cherry escreveu um excelente artigo sobre isso, apropriadamente intitulado JavaScript Scoping and Hoisting .
Leia-o. Ele lhe dará a imagem completa com todos os detalhes.

Joseph Silber
fonte
27
Esta é uma boa explicação. No entanto, sinto falta de uma solução onde você possa acessar variáveis ​​globais dentro de uma função.
DuKes0mE
1
@ DuKes0mE - você sempre pode acessar variáveis ​​globais dentro de uma função.
Joseph Silber
3
sim, e por que o caso A da postagem de abertura não está funcionando e diz que é indefinido? Eu entendo que será interpretado como uma var local em vez de global, mas como será global então?
DuKes0mE
4
para acessar a varaible global use window.value
Venkat Reddy
1
quando foi implementado este estilo de içamento variável? isso sempre foi padrão em javascript?
Dieskim
54

Fiquei um tanto desapontado com a explicação do problema aqui, mas ninguém propôs uma solução. Se você quiser acessar uma variável global no escopo da função sem que a função crie uma var local indefinida primeiro, faça referência a var comowindow.varName

Amalgovinus
fonte
8
Sim, é uma pena que os outros caras não propuseram uma solução porque este é o primeiro resultado nos resultados do Google. Eles QUASE responderam à pergunta real feita sobre ser uma armadilha no mecanismo js ... suspiro -> obrigado pela sua resposta
user1567453
2
Essa é a diferença entre conhecimento teórico e fazer sh .. feito. Obrigado por essa resposta!
hansTheFranz 01 de
Para mim, é um erro ter variado um nome global em qualquer lugar em qualquer função. Isso causa confusão, pelo menos. Na pior das hipóteses, exige uma pesquisa no Google. Obrigado
dcromley
Javascript continua me surpreendendo a cada dia que passa. Obrigado cara, a resposta foi útil.
Raf de
Obrigado pela solução, descobri isso depois que uma var global foi indefinida, mas apenas no Safari. Outros arquivos 'include' e vars globais apareceram, como 'google', então copiei a abordagem usada pelo google: window.globalVarFromJS = window.globalVarFromJS || {}; Então eu encontrei sua solução, então pensei em acrescentá-la.
Ralph Hinkley
10

Variáveis ​​em JavaScript sempre têm escopo de função ampla. Mesmo que tenham sido definidos no meio da função, eles são visíveis antes. Fenômenos semelhantes podem ser observados com o içamento de função.

Dito isso, o primeiro console.log(value)vê a valuevariável (a interna que sombreia a externa value), mas ela ainda não foi inicializada. Você pode pensar nisso como se todas as declarações de variáveis ​​fossem movidas implicitamente para o início da função ( não o bloco de código mais interno), enquanto as definições são deixadas no mesmo lugar.

Veja também

Tomasz Nurkiewicz
fonte
Eu sempre adoro palavras simples +1 :)
Jashwant
3

Existe uma variável global value, mas quando o controle entra na testfunção, outra valuevariável é declarada, o que obscurece a global. Uma vez que as declarações de variáveis ​​( mas não as atribuições ) em JavaScript são içadas ao topo do escopo em que são declaradas:

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

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

Observe que o mesmo é verdadeiro para as declarações de função, o que significa que você pode chamar uma função antes que ela pareça estar definida em seu código:

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

Também é importante notar que quando você combina os dois em uma expressão de função, a variável estará undefinedaté que a atribuição ocorra, portanto, você não pode chamar a função até que isso aconteça:

var test = function() {
    //Do stuff
};
test(); //Have to call the function after the assignment
James Allardice
fonte
0
  1. A maneira mais simples de manter o acesso a variáveis ​​externas (não apenas de escopo global) é, obviamente, tentar não redeclará-las sob o mesmo nome em funções; só não use var lá. Recomenda-se o uso de regras de nomenclatura descritivas adequadas . Com eles, será difícil acabar com variáveis ​​nomeadas como valor (este aspecto não está necessariamente relacionado ao exemplo na questão, pois o nome da variável pode ter sido fornecido para simplificar).

  2. Se a função puder ser reutilizada em outro lugar e, portanto, não houver garantia de que a variável externa realmente definida nesse novo contexto, a função Eval pode ser usada. É lento nesta operação, por isso não é recomendado para funções que exigem desempenho:

    if (typeof variable === "undefined")
    {
        eval("var variable = 'Some value';");
    }
    
  3. Se a variável de escopo externo que você deseja acessar é definida em uma função nomeada, então ela pode ser anexada à própria função em primeiro lugar e então acessada de qualquer lugar no código - seja de funções profundamente aninhadas ou manipuladores de eventos fora de todo o resto. Observe que o acesso às propriedades é muito mais lento e exigiria que você alterasse a forma como programa, por isso não é recomendado a menos que seja realmente necessário: Variáveis ​​como propriedades de funções (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
fonte
0

Eu estava tendo o mesmo problema, mesmo com variáveis ​​globais. Meu problema, descobri, era a variável global não persistir entre os arquivos html.

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

Tentei fazer referência a myVar e myVarTwo no arquivo HTML carregado, mas recebi o erro indefinido. Longa história / resumindo o dia, descobri que poderia fazer referência às variáveis ​​usando:

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

        var myHTMLVar = this.parent.myVar

        /* other stuff here */
    </script>
</html>
Nathan Sutherland
fonte