Удивлен, что глобальная переменная имеет неопределенное значение в JavaScript.


87

Сегодня я был совершенно удивлен, когда увидел, что глобальная переменная undefinedв определенном случае имеет значение.

Пример:

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

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

Выводит как

undefined
20

Вот почему движок JavaScript рассматривает глобальное значение как undefined. Я знаю, что JavaScript - это интерпретируемый язык. Как он может учитывать переменные в функции?

Это ошибка движка JavaScript?

Ответы:


175

Это явление известно как: Подъем переменных JavaScript .

Вы никогда не обращаетесь к глобальной переменной в своей функции; вы всегда обращаетесь только к локальной valueпеременной.

Ваш код эквивалентен следующему:

var value = 10;

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

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

test();

Вы все еще удивлены undefined?


Пояснение:

Это то, с чем рано или поздно сталкивается каждый программист JavaScript. Проще говоря, любые объявленные вами переменные всегда поднимаются на вершину вашего локального замыкания. Таким образом, даже если вы объявили свою переменную после первого console.logвызова, она по-прежнему считается, как если бы вы объявили ее до этого.
Однако поднимается только часть объявления; с другой стороны, назначение - нет.

Итак, при первом вызове console.log(value)вы ссылались на локально объявленную переменную, которой еще ничего не назначено; следовательно undefined.

Вот еще пример :

var test = 'start';

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

end();
alert(test);

Как вы думаете, что это предупредит? Нет, не просто читай, думай об этом. Какая ценностьtest ?

Если вы сказали что-нибудь кроме start, вы ошибались. Приведенный выше код эквивалентен этому:

var test = 'start';

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

end();
alert(test);

так что глобальная переменная никогда не затрагивается.

Как видите, независимо от того, где вы помещаете объявление переменной, оно всегда поднимается в верхнюю часть вашего локального закрытия.


Примечание:

Это также относится к функциям.

Рассмотрим этот фрагмент кода :

test("Won't work!");

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

что даст вам справочную ошибку:

Uncaught ReferenceError: тест не определен

Это сбивает с толку многих разработчиков, поскольку этот фрагмент кода отлично работает:

test("Works!");

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

Причина этого, как указано, в том, что часть назначения не поднимается. Итак, в первом примере при test("Won't work!")запускеtest переменная уже была объявлена, но ей еще не назначена функция.

Во втором примере мы не используем присвоение переменных. Скорее всего , мы используем правильный синтаксис объявления функции, которая делает получить функцию полностью водрузили.


Бен Черри написал отличную статью по этому поводу под соответствующим названием JavaScript Scoping and Hoisting .
Прочтите это. Это даст вам полную картину во всех деталях.


27
Это хорошее объяснение. Однако мне не хватает решения, в котором вы можете получить доступ к глобальным переменным внутри функции.
DuKes0mE 03

1
@ DuKes0mE - вы всегда можете получить доступ к глобальным переменным внутри функции.
Джозеф Зильбер,

3
да, и почему случай A из вводного сообщения не работает и говорит, что он не определен? Я понимаю, что он будет интерпретироваться как локальная переменная, а не глобальная, но как тогда она будет глобальной?
DuKes0mE

4
для доступа к глобальной переменной используйте window.value
Венкат Редди

1
когда был реализован этот стиль подъема переменных? всегда ли это было стандартом в javascript?
Dieskim

54

Меня несколько разочаровало объяснение проблемы, но никто не предложил решения. Если вы хотите получить доступ к глобальной переменной в области функции без функции, которая сначала создает неопределенную локальную переменную, укажите на переменную какwindow.varName


8
Да, отстой, что другие ребята не предложили решения, потому что это первый результат в результатах Google. Они ПОЧТИ ответили на фактический вопрос о том, что это ловушка в js-движке .... вздох -> спасибо за ответ
user1567453 01

2
В этом разница между теоретическими знаниями и получением дерьма. Спасибо за такой ответ!
hansTheFranz 01

Для меня ошибка, что я использовал глобальное имя где угодно в любой функции. По крайней мере, для путаницы. В худшем случае вызывает необходимость в поиске в Google. Спасибо
dcromley

Javascript продолжает удивлять меня каждый день. Спасибо, чувак, ответ оказался полезным.
Raf

Спасибо за решение, я обнаружил это после того, как глобальная переменная была undefined, но только в Safari. Появились другие файлы include и глобальные переменные, такие как google, поэтому я скопировал подход, который использовал Google: window.globalVarFromJS = window.globalVarFromJS || {}; Затем я нашел ваше решение и подумал, что добавлю к нему.
Ральф Хинкли

10

Переменные в JavaScript всегда имеют область действия на уровне всей функции. Даже если они были определены в середине функции, они видны раньше. Подобные явления могут наблюдаться при подъеме функций.

При этом первый console.log(value)видит valueпеременную (внутреннюю, которая затеняет внешнюю value), но она еще не инициализирована. Вы можете думать об этом так, как если бы все объявления переменных были неявно перемещены в начало функции (а не в самый внутренний блок кода), а определения оставлены на том же месте.

Смотрите также


Я всегда люблю простые слова +1 :)
Jashwant 04

3

Есть глобальная переменная value, но когда в testфункцию входит управление , valueобъявляется другая переменная, которая затеняет глобальную. Поскольку объявления переменных ( но не присваивания ) в JavaScript поднимаются в верхнюю часть области видимости, в которой они объявлены:

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

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

Обратите внимание, что то же самое верно и для объявлений функций, что означает, что вы можете вызвать функцию до того, как она будет определена в вашем коде:

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

Также стоит отметить, что когда вы объединяете их в выражение функции, переменная будет находиться undefinedдо тех пор, пока не произойдет присвоение, поэтому вы не можете вызвать функцию, пока это не произойдет:

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

0
  1. Самый простой способ сохранить доступ к внешним переменным (не только к глобальным) - это, конечно, попытаться не объявлять их повторно под тем же именем в функциях; просто не используйте там var . Рекомендуется использовать правильные описательные правила именования . С ними будет сложно получить переменные, названные как value (этот аспект не обязательно связан с примером в вопросе, поскольку это имя переменной могло быть дано для простоты).

  2. Если функция может быть повторно использована в другом месте и, следовательно, нет гарантии, что внешняя переменная действительно определена в этом новом контексте, можно использовать функцию Eval . Эта операция выполняется медленно, поэтому ее не рекомендуется использовать для функций, требующих высокой производительности:

    if (typeof variable === "undefined")
    {
        eval("var variable = 'Some value';");
    }
    
  3. Если переменная внешней области видимости, к которой вы хотите получить доступ, определена в именованной функции, тогда она может быть прикреплена к самой функции в первую очередь, а затем доступна из любого места в коде - будь то из глубоко вложенных функций или обработчиков событий вне все остальное. Обратите внимание, что доступ к свойствам намного медленнее и потребует от вас изменения способа программирования, поэтому это не рекомендуется, если это действительно не нужно: Переменные как свойства функций (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

Я столкнулся с той же проблемой даже с глобальными переменными. Я обнаружил, что моя проблема заключалась в том, что глобальная переменная не сохраняется между HTML-файлами.

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

Я попытался сослаться на myVar и myVarTwo в загруженном HTML-файле, но получил неопределенную ошибку. Короче говоря, я обнаружил, что могу ссылаться на переменные, используя:

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

        var myHTMLVar = this.parent.myVar

        /* other stuff here */
    </script>
</html>
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.