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?

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.


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


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


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

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");
    

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>
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.