Здивований, що глобальна змінна має невизначене значення в 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 .
Читати. Це дасть вам повну картину з усіма деталями.


27
Це гарне пояснення. Однак я сумую за рішенням, де ви можете отримати доступ до глобальних змінних всередині функції.
DuKes0mE

1
@ DuKes0mE - ви завжди можете отримати доступ до глобальних змінних всередині функції.
Джозеф Сільбер

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

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

1
коли був застосований цей стиль підняття змінних? це завжди було стандартним для javascript?
Dieskim

54

Я був дещо розчарований, що тут пояснюється проблема, але ніхто не запропонував рішення. Якщо ви хочете отримати доступ до глобальної змінної в області функцій, без функції, яка спочатку робить невизначений локальний var, посилайтеся на var якwindow.varName


8
Так, погано, що інші хлопці не пропонували рішення, оскільки це перший результат у результатах Google. Вони майже ВСЕ відповіли на фактичне запитання про те, що це підводний камінь у двигуні js .... зітхання -> дякую за вашу відповідь
user1567453

2
Ось у чому різниця між теоретичними знаннями та виконанням .. Дякую за цю відповідь!
hansTheFranz

Для мене помилковим є те, що я міняв глобальне ім'я в будь-якому місці будь-якої функції. Щонайменше робить плутанину. У гіршому випадку потребує пошуку в Google. Дякую
dcromley

Javascript постійно дивує мене щодня, що минає. Дякую, відповідь була корисною.
Раф,

Дякую за рішення, я знайшов це після того, як глобальний var не був визначений, але лише в Safari. З'явились інші файли 'include' та глобальні vars, такі як 'google', тому я скопіював підхід, який використовував google: window.globalVarFromJS = window.globalVarFromJS || {}; Тоді я знайшов ваше рішення, тому подумав, що додам до нього.
Ральф Хінклі,

10

Змінні в JavaScript завжди мають широкий спектр функцій. Навіть якщо вони були визначені в середині функції, вони видно раніше. Подібні явища можуть спостерігатися при підйомі функцій.

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

Дивіться також


Я завжди люблю прості слова +1 :)
Jashwant

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 . Рекомендується використовувати належні правила опису імен . З цими варіантами буде важко отримати змінні, названі як значення (цей аспект не обов'язково пов'язаний із прикладом у питанні, оскільки це ім'я змінної могло бути дане для простоти).

  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.