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変数はすでに宣言されていますが、まだ関数が割り当てられていません。

2番目の例では、変数の割り当てを使用していません。むしろ、適切な関数宣言構文を使用してます。これにより、関数が完全に引き上げられます。


Ben Cherryは、これに関する優れた記事を、適切にJavaScript Scoping andHoistingというタイトルで書いています。
それを読んで。それはあなたに完全な詳細で全体像を与えるでしょう。


27
これは良い説明です。ただし、関数内のグローバル変数にアクセスできるソリューションがありません。
DuKes0mE 2014

1
@ DuKes0mE-関数内のグローバル変数にいつでもアクセスできます。
ジョセフシルバー2014

3
はい、そしてなぜオープニングポストのケースAが機能せず、未定義と言っているのですか?グローバルではなくローカル変数として解釈されることは理解していますが、グローバルになるにはどうすればよいでしょうか。
DuKes0mE 2014

4
グローバル変数使用window.valueにアクセスするには
Venkat Reddy

1
このスタイルの可変巻き上げはいつ実装されましたか?これはJavaScriptでは常に標準でしたか?
Dieskim 2017年

54

ここでの問題が説明されていることに少しがっかりしましたが、誰も解決策を提案しませんでした。関数が未定義のローカル変数を最初に作成せずに関数スコープ内のグローバル変数にアクセスする場合は、変数を次のように参照します。window.varName


8
ええ、これがグーグルの結果の最初の結果であるため、他の人が解決策を提案しなかったのは残念です。彼らは、それがjsエンジンの落とし穴であるという実際の質問に
ほとんど

2
それが理論的知識とsh ..を成し遂げることの違いです。その答えをありがとう!
hansTheFranz 2017

私にとって、関数のどこかにグローバル名を付けたのは間違いです。少なくとも混乱を招きます。最悪の場合、グーグル検索の必要性になります。ありがとう
dcromley 2018年

Javascriptは、私を驚かせ続けます。ありがとう、答えは役に立ちました。
のRaf

解決策のおかげで、グローバル変数が未定義であるがSafariでのみこれを見つけました。他の「include」ファイルと「google」などのグローバル変数が表示されたので、googleが使用したアプローチをコピーしました:window.globalVarFromJS = window.globalVarFromJS || {}; それから私はあなたの解決策を見つけたので、私はそれに追加しようと思いました。
ラルフヒンクリー

10

JavaScriptの変数は、常に関数全体のスコープを持っています。関数の途中で定義されていても、前に表示されます。機能巻き上げでも同様の現象が見られる場合があります。

そうは言っても、最初console.log(value)value変数(外側をシャドウする内側の変数value)が表示されますが、まだ初期化されていません。定義は同じ場所に残されたまま、すべての変数宣言が暗黙的に関数の先頭(最も内側のコードブロックではない)に移動されたかのように考えることができます。

も参照してください


私はいつも簡単な言葉が大好きです+1 :)
Jashwant 2012

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
}

また、この2つを関数式に組み合わせると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>

ロードされたHTMLファイルでmyVarとmyVarTwoを参照しようとしましたが、未定義のエラーが発生しました。長い話/短い日、私は以下を使用して変数を参照できることを発見しました:

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

        var myHTMLVar = this.parent.myVar

        /* other stuff here */
    </script>
</html>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.