Sorprendido de que la variable global tenga un valor indefinido en JavaScript


87

Hoy, me sorprendí completamente cuando vi que una variable global tiene undefinedvalor en cierto caso.

Ejemplo:

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

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

Da salida como

undefined
20

Aquí, ¿por qué el motor de JavaScript considera el valor global como undefined. Sé que JavaScript es un lenguaje interpretado. ¿Cómo puede considerar variables en la función?

¿Es una trampa del motor JavaScript?

Respuestas:


175

Este fenómeno se conoce como: Elevación variable de JavaScript .

En ningún momento accede a la variable global en su función; solo está accediendo a la valuevariable local .

Su código es equivalente a lo siguiente:

var value = 10;

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

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

test();

¿Todavía te sorprende que te estés recibiendo undefined?


Explicación:

Esto es algo con lo que todos los programadores de JavaScript se topan tarde o temprano. En pocas palabras, lo variables que se declaran siempre está izada a la parte superior de su clausura local. Entonces, aunque declaró su variable después de la primera console.logllamada, todavía se considera como si la hubiera declarado antes.
Sin embargo, solo se iza la parte de la declaración; la asignación, por otro lado, no lo es.

Entonces, cuando llamó por primera vez console.log(value), estaba haciendo referencia a su variable declarada localmente, que aún no tiene nada asignado; por lo tanto undefined.

Aquí hay otro ejemplo :

var test = 'start';

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

end();
alert(test);

¿Qué crees que alertará esto? No, no se limite a seguir leyendo, piénselo. ¿Cuál es el valor de test?

Si dijiste algo más que start, estabas equivocado. El código anterior es equivalente a esto:

var test = 'start';

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

end();
alert(test);

para que la variable global nunca se vea afectada.

Como se puede ver, no importa donde usted pone su declaración de variables, siempre es izada a la parte superior de su clausura local.


Nota al margen:

Esto también se aplica a las funciones.

Considere este fragmento de código :

test("Won't work!");

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

que le dará un error de referencia:

Error de referencia no detectado: la prueba no está definida

Esto desconcierta a muchos desarrolladores, ya que este código funciona bien:

test("Works!");

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

La razón de esto, como se indicó, es que la parte de asignación no está izada. Entonces, en el primer ejemplo, cuando test("Won't work!")se ejecutó, la testvariable ya ha sido declarada, pero aún no se le ha asignado la función.

En el segundo ejemplo, no usamos la asignación de variables. Más bien, estamos usando la sintaxis de declaración de función adecuada, lo que hace que la función sea completamente mejorada.


Ben Cherry ha escrito un artículo excelente sobre esto, apropiadamente titulado Alcance y elevación de JavaScript .
Léelo. Le dará la imagen completa con todos los detalles.


27
Ésta es una buena explicación. Sin embargo, echo de menos una solución en la que pueda acceder a variables globales dentro de una función.
DuKes0mE

1
@ DuKes0mE: siempre puede acceder a variables globales dentro de una función.
Joseph Silber

3
sí, y ¿por qué el caso A de la publicación de apertura no funciona y dice que no está definido? Entiendo que se interpretará como una var local en lugar de global, pero ¿cómo va a ser global entonces?
DuKes0mE

4
para acceder a window.value de uso variable global
Venkat Reddy

1
¿Cuándo se implementó este estilo de elevación variable? ¿Esto siempre ha sido estándar en javascript?
Dieskim

54

Me decepcionó un poco que se explicara el problema aquí, pero nadie propuso una solución. Si desea acceder a una variable global en el alcance de la función sin que la función haga primero una var local indefinida, haga referencia a la var comowindow.varName


8
Sí, apesta que los otros chicos no propusieran una solución porque este es el primer resultado en los resultados de Google. CASI respondieron la pregunta real sobre que era un error en el motor js ... suspiro -> gracias por su respuesta
user1567453

2
Esa es la diferencia entre el conocimiento teórico y hacer las cosas. ¡Gracias por esa respuesta!
hansTheFranz

Para mí, es un error haber cambiado un nombre global en cualquier lugar de cualquier función. Da lugar a confusión, al menos. Hace que sea necesario realizar una búsqueda en Google, en el peor de los casos. Gracias
dcromley

Javascript sigue sorprendiéndome todos los días que pasa. Gracias, hombre, la respuesta fue útil.
Raf

Gracias por la solución, encontré esto después de que una var global no estaba definida pero solo en Safari. Aparecieron otros archivos 'include' y variables globales como 'google', así que copié el enfoque que usó google: window.globalVarFromJS = window.globalVarFromJS || {}; Luego encontré su solución, así que pensé en agregarle algo.
Ralph Hinkley

10

Las variables en JavaScript siempre tienen un alcance de función amplia. Incluso si se definieron en el medio de la función, son visibles antes. Se pueden observar fenómenos similares con la función de elevación.

Dicho esto, el primero console.log(value)ve la valuevariable (la interior que sombrea la exterior value), pero aún no se ha inicializado. Puede pensar en ello como si todas las declaraciones de variables se movieran implícitamente al principio de la función ( no al bloque de código más interno), mientras que las definiciones se dejan en el mismo lugar.

Ver también


Siempre amo las palabras simples +1 :)
Jashwant

3

Hay una variable global value, pero cuando el control ingresa a la testfunción, valuese declara otra variable, que ensombrece la global. Dado que las declaraciones de variables ( pero no las asignaciones ) en JavaScript se elevan a la parte superior del alcance en el que se declaran:

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

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

Tenga en cuenta que lo mismo ocurre con las declaraciones de funciones, lo que significa que puede llamar a una función antes de que parezca estar definida en su código:

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

También vale la pena señalar que cuando combina los dos en una expresión de función, la variable será undefinedhasta que se lleve a cabo la asignación, por lo que no puede llamar a la función hasta que eso suceda:

var test = function() {
    //Do stuff
};
test(); //Have to call the function after the assignment

0
  1. La forma más sencilla de mantener el acceso a las variables externas (no solo de alcance global) es, por supuesto, intentar no volver a declararlas con el mismo nombre en las funciones; simplemente no use var allí. Se aconseja el uso de reglas de nomenclatura descriptivas adecuadas . Con ellos, será difícil terminar con variables denominadas como valor (este aspecto no está necesariamente relacionado con el ejemplo de la pregunta, ya que este nombre de variable podría haberse dado por simplicidad).

  2. Si la función puede reutilizarse en otro lugar y, por lo tanto, no hay garantía de que la variable externa realmente esté definida en ese nuevo contexto, la función Eval puede usarse. Es lento en esta operación, por lo que no se recomienda para funciones de alto rendimiento:

    if (typeof variable === "undefined")
    {
        eval("var variable = 'Some value';");
    }
    
  3. Si la variable de alcance externo a la que desea acceder está definida en una función con nombre, entonces podría estar adjunta a la función en sí en primer lugar y luego acceder desde cualquier lugar del código, ya sea desde funciones profundamente anidadas o controladores de eventos fuera todo lo demas. Tenga en cuenta que acceder a las propiedades es mucho más lento y requeriría que cambie la forma en que programa, por lo que no se recomienda a menos que sea realmente necesario: Variables como propiedades de funciones (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

Me estaba encontrando con el mismo problema incluso con variables globales. Descubrí que mi problema era que la variable global no persistía entre archivos html.

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

Intenté hacer referencia a myVar y myVarTwo en el archivo HTML cargado, pero recibí el error indefinido. En pocas palabras, descubrí que podía hacer referencia a las variables usando:

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

        var myHTMLVar = this.parent.myVar

        /* other stuff here */
    </script>
</html>
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.