昇降機構
JavaScript の変数宣言にはホイスティング メカニズムがあり、JavaScript エンジンが実行されると、すべての変数宣言が現在のスコープの先頭に引き上げられます。
最初にコードの一部を確認してください
var v = "hello"; (function(){ console.log(v); var v = "world"; })();
このコードを実行すると結果はどうなりますか?
答えは次のとおりです: 未定義
このコードは 2 つの問題を示しています。
まず、関数スコープ内の変数 v は、上位スコープ変数 v をカバーします。コードへの変更を少なくします
var v = "hello"; if(true){ console.log(v); var v = "world"; }
出力結果は「hello」で、JavaScript にブロックレベルのスコープがないことを示しています。関数は、独自のスコープを持つ JavaScript の唯一の構造です。
2 番目に、関数スコープ内で変数 v の宣言がプロモートされます。したがって、最初のコードは次と同等です:
var v = "hello"; (function(){ var v; //declaration hoisting console.log(v); v = "world"; })();
宣言、定義、初期化
宣言は名前の存在を宣言し、定義は名前に記憶領域を割り当て、初期化は名前によって割り当てられた記憶領域に初期値を割り当てます。
C を使用してこれら 3 つの概念を表現します
extern int i;//これは、名前 i がすでにどこかに存在することを示すステートメントです
int i;//これは、名前 i を宣言して定義し、i
に記憶領域を割り当てます。
i = 0;//これは初期化名 i で、初期値 0
が割り当てられます。
これは JavaScript の場合です
var v;//変数 v を宣言
v = "hello";//変数 v
を (定義して) 初期化します
JavaScript は動的言語であるため、その変数の型は固定されておらず、その記憶領域のサイズは初期化と代入によって変化します。したがって、その変数の「定義」は従来の静的言語のそれと同じではありません。その定義は無関係です。
昇格宣言
現在のスコープ内の宣言は、変数や関数の宣言を含め、スコープの先頭に昇格されます
(function(){ var a = "1"; var f = function(){}; var b = "2"; var c = "3"; })();
変数 a、f、b、c の宣言は、次のように関数スコープの先頭に昇格されます:
(function(){ var a,f,b,c; a = "1"; f = function(){}; b = "2"; c = "3"; })();
関数式はプロモートされないことに注意してください。これが関数式と関数宣言の違いでもあります。 2 つの違いを詳しく見てみましょう:
(function(){ //var f1,function f2(){}; //hoisting,被隐式提升的声明 f1(); //ReferenceError: f1 is not defined f2(); var f1 = function(){}; function f2(){} })();
上記コードの関数宣言 f2 はプロモートされているので、f2 を先に呼び出しても問題ありません。変数 f1 もプロモートされますが、プロモート後の f1 の値は不定であり、関数式の実行時に実際の初期値が代入されます。したがって、ステートメントのみがプロモートされます。
名前解決の順序
JavaScript の名前は 4 つの方法でスコープに入ります。その優先順位は次のとおりです。
1. 組み込み言語: すべてのスコープに this および argument キーワードが含まれます
2. 仮パラメータ: 関数のパラメータは関数スコープ
内で有効です。
3. 関数宣言: 関数 foo() {}
の形で
4. 変数宣言: var bar;
名前宣言の優先順位は上に示したとおりです。つまり、変数の名前が関数の名前と同じ場合、その順序に関係なく、関数の名前が変数の名前をオーバーライドします。コードの中で。ただし、名前はコードに記述された順序で初期化され、上記の優先順位の影響を受けません。コードを見てください:
(function(){ var foo; console.log(typeof foo); //function function foo(){} foo = "foo"; console.log(typeof foo); //string })();
仮パラメータに同じ名前の変数が複数ある場合、同じ名前の最後のパラメータが定義されていない場合でも、同じ名前の最後のパラメータが同じ名前の他のパラメータを上書きします。
上記の名前解決の優先順位には、言語の組み込みの名前引数をオーバーライドする機能などの例外があります。
名前付き関数式
関数式には関数宣言と同じように名前を付けることができますが、これでは関数式が関数宣言になるわけではありません。名前付き関数式の名前はネームスペースに入らず、昇格されません。
f();//TypeError: f は関数ではありません
foo();//ReferenceError: foo が定義されていません
var f = function foo(){console.log(typeof foo);};
f();//関数
foo();//ReferenceError: foo が定義されていません
名前付き関数式の名前は、関数のスコープ内でのみ有効です。
次の例を見てみましょう:
var myval = "my global var"; (function() { console.log(myval); // log "my global var" })();
上記のコードは明らかに「my global var」を出力しますが、上記のコードを次のように少し変更すると、
var myval = "my global var"; (function() { console.log(myval); // log "undefined" var myval = "my local var"; })();
実行結果は未定義が出力されます。これは、上記のコードが次のコードと同等であるためです。
var myval = "my global var"; (function() { var myval; console.log(myval); // log "undefined" myval = "my local var"; })();
ただし、この昇格メカニズムは通常の変数だけでなく関数にも反映されます。たとえば、次のコードは正しく実行できません:
(function() { fun(); // Uncaught TypeError: undefined is not a function var fun = function() { console.log("Hello!"); } })();
(function() { var fun; fun(); // Uncaught TypeError: undefined is not a function fun = function() { console.log("Hello!"); } })();
もちろん、このような関数の定義方法を「関数式」と呼び、次のような「関数宣言」方式であれば昇格機構は全く問題ありません。
(function() { fun(); function fun() { console.log("Hello!"); // log "Hello!" } })();
これは、関数宣言と関数式の主な違いでもあります。