実際、このコード行にはもっと多くの知識が含まれています。例えば、コンパイルの原理、スコープ、LHSとRHSクエリなどです。ここでは、その謎めいたベールを1つずつ開けていきましょう。
コンパイルの原理
JavaScriptはコンパイル言語であり、プリコンパイルされていないため、JSのコンパイルはコードが実行される前に非常に短い時間で行われます。
プログラムのソースコードは実行される前に、曖昧性解消、構文解析、コード生成の3つの段階を経ます。
- セグメンテーション/論理解析
この段階は、曖昧性解消とも字句解析とも呼ばれます。この2つの違いは非常に微妙で理解しにくいので、区別の仕方を知っておいて損はないと思います。
このプロセスでは、文字列で構成されるソースコードを、字句単位として数えられる意味のあるコードの塊に分解します。var a = 2 は、字句単位 var、a、=、2 に分解されます。 - 解析/構文解析 このプロセスのタスクは、曖昧性解消フェーズで生成された語彙単位のストリームを、レベルごとに入れ子になっ た要素で構成されるプログラムの構文構造を表すツリーに変換することです。この数がビッグネーム抽象構文木です。
- コード生成 コンピュータが認識できるのはバイナリだけであり、ASTをバイナリの実行可能コードに変換するプロセスがコード生成です。
スコープ
reactでは、コンポーネントの状態を表すのにstateがよく使われます。ステートという単語がなければ、プログラムは簡単なタスクを実行することはできますが、プログラムの柔軟性が制限されてしまいます。
プログラミング言語では、状態を表すために変数が使われます。ほとんどのプログラミング言語には、変数の値を保存したり、アクセスしたり、変更したりする機能があり、この変数の値を保存したりアクセスしたりする機能こそが、プログラムに状態をもたらすのです。
変数の場合、どこに格納し、必要なときにどうやって見つけるかを考える必要があります。そのためには、変数を保存したりアクセスしたりするためのルールセットをきちんと設計する必要があります。スコープとは、変数を格納したりアクセスしたりするためのルールのことです。
スコープには3つのタイプがあります:
- グローバルスコープ
- 関数スコープ
- ブロックレベルのスコープ
これら3種類のスコープについては、後でクロージャについて説明するときに詳しく説明します。結局のところ、クロージャはスコープとプリコンパイルプロセスの両方と切っても切れない関係にあります。
スコープをよりよく理解するためには、エンジンとコンパイラを理解することも重要です。
- エンジン
は、JSプログラムのコンパイルと実行のプロセス全体を最初から最後まで担当します。 - コンパイラ
構文解析とコード生成を担当します。
var a = 2 を見たとき、あなたはこれを宣言と考えますが、エンジンはここに2つの宣言があると考えます。1つはコンパイル段階でコンパイラが処理するもの、もう1つは実行時にエンジンが処理するものです。
以下はvar a = 2を分解したものです。コンパイラーはまずコード行を字句単位に分解し、次にこれらの字句単位を解析してツリー構造にします。最後に実行コードが生成されます。
実際、コンパイラは次のような処理を行います。
1.コンパイラは、var aに遭遇すると、変数aが現在のスコープにすでに存在するかどうかを尋ね、存在する場合は変数宣言を無視してコンパイルを進めます。存在する場合は変数宣言を無視してコンパイルを続行し、存在しない場合は現在のスコープにaという名前の変数を宣言します。
2.次に、コンパイラは a = 2 の代入操作を実行するための実行時コードを生成します。実行段階では、エンジンは現在のスコープで a 変数を探し、a 変数が見つかればそれを使用し、見つからなければ引き続き変数を探します。
LHSとRHS(左手/右手検索)
この2つの方法は、エンジンが変数を探す方法です。
LHS と RHS は「代入の左手または右手」という意味で、「= 代入演算子の左手または右手」という意味ではありません。代入には他にもいくつかの形式があるので、概念的には「誰が代入のターゲットか」「誰が代入のソースか」と考えた方がよいでしょう。
- LHSとRHSはどちらも、エンジンが変数に照会るときに発生します。
- LHS変数は代入されるか、メモリに書き込まれます。
- RHS 変数のルックアップまたはコンテンツからの読み込み ハードドライブからテキストファイルを開くことを考えてください。
特徴
- すべてのスコープですべての変数が検索されます。
- 厳密モードでは、必要な変数が見つからない場合、エンジンは ReferenceError 例外をスローします。
- 非厳密モードでは、LHSは自動的にグローバル変数を作成します。
- クエリが成功した後、変数が不当な方法で操作されると TypeError がスローされます。
練習のための例を示します。
var a = 2;
a();
LHSには3つの場所があります: c = ...c = ... ; a = 2 (暗黙の変数代入), b = ...
RHSには4つの場所があります: foo(2... , = a; aとbは暗黙の変数代入。a と b は return
スコープ
スコープの入れ子は、ブロックや関数が別のブロックや関数の中に入れ子になっている場合に発生します。そのため、現在のスコープで変数が見つからない場合、エンジンは変更を見つけるか一番外側のスコープに到達するまで、ネストされた外側のスコープを探し続けます。
スコープ・チェイニングは、実はプリコンパイルと密接に関係しています。
プリコンパイルは4段階のプロセスです:
- AOオブジェクトの作成
- フォーマル・パラメータと変数の宣言を見つけ、変数名とフォーマル・パラメータ名を、値がundefinedのAO属性名として使用します。
- 実パラメータと形式パラメータの値を統一します。
- 関数本体内の関数宣言を見つけ、関数本体に値を代入します。
function foo(a) {
var b = a;
return a + b;
}
var c = foo(2);
1.AOの作成
を作成します。
2.形式パラメータと変数の宣言を見つけ、変数名と形式パラメータ名をundefinedの値でAO属性名として使用します。
AO { a: undefined, b: undefined, }.
3.実パラメータ値と形式パラメータ値の統一
AO { a: 1, b: undefined, } 4.
4.関数本体内の関数宣言を見つけ、その値を関数本体に代入します。
AO { a: function a(){}, b: undefined,d: function d() {}} 4.
プリコンパイルが完了すると、エンジンはコードの実行を開始できます。
function fn(a) {
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
var b = function () {};
console.log(b);
function d() {}
}
fn(1);
つまり、上記のコードの結果は function a(){},123,123,function(){} となります。
もう1つ練習してみましょう。
function fn(a) {
console.log(a);//この時点でのAOは関数a()である。{}
var a = 123;// var a プリコンパイル時にすでに実行されているので、再度実行する必要はない。= 123, AOaのaは、関数から: 123
console.log(a);// 123
function a() {};// プリコンパイルはすでに実行されているので、再度実行する必要はない。
console.log(a);// AO真ん中のaはまだ123である。
var b = function () {};// var bすでに実行されているb= function(){},AOこの場合、bの値はundefinedから無名関数に変更されている。
console.log(b);// function() {}
function d() {}:// コンパイルの段階ですでに実行されているのだから、もう実行されない
}
fn(1);
プリコンパイルと実行が完了すると、エンジンはコードの実行を開始します。
function a(age) {
console.log(age);
var age = 20;
console.log(age);
function age() {
}
console.log(age);
}
a(18);
つまり、上記の結果は function age(){},20,20 となります。
プリコンパイルとAOを理解すれば、クロージャを理解するのがずっと簡単になります。





