Удивлен, что глобальная переменная имеет неопределенное значение в JavaScript - PullRequest
79 голосов
/ 31 января 2012

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

Пример:

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

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

Дает вывод как

undefined
20

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

Это ловушка из движка JavaScript?

Ответы [ 5 ]

163 голосов
/ 31 января 2012

Это явление известно как: Подъем переменной 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 .
Прочтите его.Это даст вам полную картину в деталях.

48 голосов
/ 29 августа 2014

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

10 голосов
/ 31 января 2012

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

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

Смотри также

3 голосов
/ 31 января 2012

Существует глобальная переменная 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 голосов
/ 27 февраля 2019
  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");
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...