JS コンパイル原理#
var name = "rose";
上記のコードは、JS では次のように表示されます:
var name; // コンパイルフェーズで処理されます
name = "rose"; // 実行フェーズで処理されます
JS コンパイルは主に 2 つのフェーズに分かれます:コンパイルフェーズと実行フェーズ。
コンパイルフェーズ#
このフェーズでは、コンパイラが主役です。
- JS はスコープを探し、変数
name
が存在するかどうかを確認します。- もし既に存在していれば、何もせずに
var name
の宣言を無視して、次のコンパイルに進みます。 - 存在しなければ、現在のスコープに新しい
name
変数を追加します。
- もし既に存在していれば、何もせずに
- コンパイラはエンジンが実行に必要なコードを生成し、プログラムは実行フェーズに入ります。
実行フェーズ#
このフェーズでは、JS エンジンが主役です。
- JS エンジンは実行時に現在のスコープを探し、
name
という変数が存在するかどうかを確認します。- 存在すれば、値を代入します。
- 存在しなければ、現在のスコープには存在しないことを示します。その後、親スコープを見て
name
が存在するかどうかを確認します。存在しなければ、さらに上のスコープを探します。 - 最終的に見つからなければ、エラーが発生します。
スコープがスコープに入れ子になっていることを意味するスコープチェーン。
スコープ#
変数の基本的な機能は、変数内の値を格納し、その変数にアクセスおよび変更することができることです。そして、変数の格納とアクセスのルールがスコープです。
グローバルスコープ#
関数の外部またはコードブロックの外部のトップレベルスコープはグローバルスコープであり、その中の変数はグローバル変数です。
var name = "rose"; // グローバルスコープ
function showName() {
// 関数スコープ
console.log(name);
}
{
name = "test"; // ブロックスコープ
}
showName(); //test
グローバル変数は、グローバルスコープ、関数スコープ、ブロックスコープのいずれでも正常にアクセスできることがわかります。
関数スコープ#
関数内部のスコープは関数スコープです。
function showName() {
var name = "jack"; // 関数スコープ
}
showName(); //メソッド呼び出し
{
console.log(name); //ブロックスコープ、Uncaught ReferenceError: name is not defined
}
console.log(name); //グローバルスコープ、Uncaught ReferenceError: name is not defined
関数内部の変数は、グローバルスコープおよびブロックスコープではアクセスできず、関数内部でのみアクセスできるため、関数内部の変数はローカル変数とも呼ばれます。
ブロックスコープ#
ES6
で新しく導入されたlet
およびconst
キーワードは、ブロックスコープを持っています。ブロックスコープは、そのコードブロック内でのみ有効であり、中括弧{}
で囲まれている場合、中括弧はコードブロックを表します。const
で宣言された変数もローカル変数と呼ばれます。
{
let name='rose';
}
console.log(name); //Uncaught ReferenceError: name is not defined
function showName{
console.log(name);
}
showName(); //Uncaught ReferenceError: name is not defined
ブロックスコープ内の変数は、コードブロックの外部からアクセスできなくなります。
スコープチェーン#
スコープとスコープのネストにより、スコープチェーンが生まれます。スコープチェーンの検索は外側に向かって行われます。
変数の巻き上げ#
name = "rose";
console.log(name); //rose
var name;
このコードは正常に実行され、エラーは発生しません。
JS の視点では、実際のコードは次のようになります:
var name;
name = "rose";
console.log(name); // rose
let
およびconst
のコード:
name = "rose";
console.log(name); //Uncaught ReferenceError: Cannot access 'name' before initialization
let name;
let
、const
は変数の巻き上げを禁止します。const
は宣言後に値を設定する必要があります。
let、const、var の違い#
- ブロックスコープ:ブロックスコープは
{}
で囲まれた範囲で有効であり、let
およびconst
はブロックスコープを持ち、var
にはブロックスコープはありません。
ブロックスコープは、ES5
の 2 つの問題を解決します:
- 内側の変数が外側の変数を上書きする可能性がある
- カウントに使用されるループ変数がグローバル変数に漏れる可能性がある
- 変数の巻き上げ:
var
は変数の巻き上げがありますが、let
およびconst
には変数の巻き上げはありません。つまり、変数は宣言後にのみ使用できます。それ以外の場合はエラーが発生します。 - グローバルへのプロパティの追加:ブラウザのグローバルオブジェクトは window であり、Node のグローバル変数は global です。
var
で宣言された変数はグローバル変数として追加され、その変数はグローバルオブジェクトのプロパティとして追加されますが、let
およびconst
は追加されません。 - 重複の宣言:
var
で変数を宣言する場合、同じスコープ内で変数を重複して宣言することができます。後で宣言された変数が前に宣言された変数を上書きします。一方、let
およびconst
では、同じスコープ内で変数を重複して宣言することはできません。 - 一時的な死区:
let
、const
キーワードを使用して変数を宣言する場合、その変数は使用できません。これは、構文的には一時的な死区と呼ばれます。
var
で宣言された変数は一時的な死区が存在しません。
- 初期値の設定:変数を宣言する際、
var
およびlet
は初期値を設定する必要はありません。一方、const
で変数を宣言する場合は初期値を設定する必要があります。 - ポインタの指向:
let
およびconst
は、ES6 で導入された変数の作成に使用される構文です。let
で作成された変数はポインタの指向を変更できます(再代入できます)。一方、const
で宣言された変数はポインタの指向を変更することはできません(再代入はできません)。
一時的な死区#
var name = "rose";
{
name = "bob";
let name; //Uncaught ReferenceError: Cannot access 'name' before initialization
}
もしブロック内に let や const が存在する場合、そのブロック内で宣言された変数 name には一時的な死区の制限が追加されます。
JS は明確に name が現在のコードブロック内で let で宣言されていることを認識しているため、この変数 name には一時的な死区の制限が追加され、外に出てくることはありません。
したがって、上記のlet name;
を削除しても、プログラムは正常に実行され、name の値も正常に blob に変更されます。これは、スコープチェーンのルールに従って、外に出てくることができるからです。