Javascript の変数宣言構文の違い (グローバル変数を含む) 質問する

Javascript の変数宣言構文の違い (グローバル変数を含む) 質問する

変数の宣言には違いがありますか:

var a=0; //1

...こちらです:

a=0; //2

...または:

window.a=0; //3

グローバルな範囲で?

ベストアンサー1

はい、いくつか違いはありますが、実際にはそれほど大きな違いではありません (ただし、#2 は除きます。a = 0; これは、A) 実行しないことを強くお勧めします。また、B) 厳密モードではエラーになります)。

4 番目の方法があり、ES2015 (ES6) ではさらに 2 つあります。4 番目の方法は最後に追加しましたが、ES2015 の方法は #1 の後に挿入しました (理由は後でわかります)。つまり、次のようになります。

var a = 0;                             // 1
let a = 0;                             // 1.1 (new with ES2015)
const a = 0;                           // 1.2 (new with ES2015)
a = 0;                                 // 2
window.a = 0; /*or*/ globalThis.a = 0; // 3
this.a = 0;                            // 4

これらの声明は、

1.var a = 0;

これにより、グローバル変数が作成され、これはグローバルオブジェクトwindowブラウザ(またはglobalThisglobal は ES2020 で追加されました。または、thisグローバル スコープで を介して削除できます。他のプロパティとは異なり、このプロパティは を介し​​て削除できませんdelete

仕様上、これは識別子バインディングを作成する。オブジェクト環境レコードのために地球環境グローバル オブジェクトは、グローバル環境のオブジェクト環境レコードの識別子バインディングが保持される場所であるため、これはグローバル オブジェクトのプロパティになります。これが、このプロパティが削除不可能な理由です。これは単なるプロパティではなく、識別子バインディングであり、識別子は削除できません。

バインディング (変数) は、コードの最初の行が実行される前に定義されます (var以下の「いつ発生するか」を参照)。

これによって作成されるプロパティは列挙可能です (非常に古い IE8 以前を除く)。


1.1let a = 0;

これは、グローバル オブジェクトのプロパティではないグローバル変数を作成します。これは ES2015 以降の新機能です。

仕様上、これは識別子バインディングを作成する。宣言的環境記録グローバル環境ではなくオブジェクト環境レコードに分割されている点がグローバル環境の特徴です。環境記録1 つはグローバル オブジェクト (オブジェクト環境レコード)に含まれるすべての古い内容用で、もう 1 つはグローバル オブジェクトには含まれず、代わりにグローバル環境の宣言的let環境レコードに含まれるすべての新しい内容 ( 、const、および によって作成された関数) 用です。class

バインディングは、その外側のブロック内のステップバイステップのコードが実行される前に作成されます(この場合は、グローバル コードが実行される前)。ただし、ステップバイステップの実行がステートメントに到達するまで、バインディングにはいかなる方法でもアクセスletできません。実行がステートメントに到達するとlet、変数にアクセスできるようになります (以下の「いつletconst何が起こるか」を参照してください)。バインディングが作成されてから(スコープに入るとき)、アクセス可能になるまで(コード実行が に到達するまで) の時間は、 Temporal Dead Zonelet [TMZ]と呼ばれます。バインディングがその状態にある間、バインディングからの読み取りやバインディングへの書き込みを試行すると、ランタイム エラーになります。

(バインディングがアクセス可能かどうかを表す仕様用語は「初期化されている」かどうかですが、「初期化されている」という使用法と、ステートメントlet[let a = 10;let a;] に初期化子があることを混同しないでください。これらは無関係です。 によって定義された変数は、に到達するとlet a;によって初期化されます。)undefinedlet


1.2const a = 0;

グローバル オブジェクトのプロパティではないグローバル定数を作成します。

バインディングは、値を変更できないことを示すフラグがあることを除けば、バインディング (TMZ などを含む)constとまったく同じです。その意味の 1 つは、 の初期値 (決して変更されない値) を提供するために、初期化子 ( 部分)を用意する必要があるということです。let= valueconst

を使用するとconst、次の 3 つのことが実現します。

  1. 定数に代入しようとすると、ランタイム エラーが発生します (ほとんどの IDE では、それよりも積極的にフラグが立てられます)。
  2. 他のプログラマーのために、その不変の性質を文書化します。
  3. JavaScript エンジンが、 の値が変更されないことを前提に最適化できるようにしますconst(後で書き込まれるかどうかを追跡する必要がなく、つまり、実質的に定数であるかどうかを確認する必要がありません)。

constの値が決して変わらないということは、 が参照するオブジェクトが不変であるという意味ではないことを理解することが重要です。そうではありません。 の値は変更できないため、別のオブジェクトを参照する (またはプリミティブを含む)とconstいうだけです。const

// This is fine:
const x1 = {a: 1};
console.log(x1.a); // 1
x1.a = 2;
//^^^^^^−−− No problem, just changing the object's state, not the value in the `const` (the object reference)
console.log(x1.a); // 2

// This is not:
const x2 = {a: 1};
console.log(x2.a); // 1
x2 = {a: 2};
// ^−−−−−−− Error here ("TypeError: Assignment to constant variable"),
// you can't change the value of a `const`
console.log(x2.a);


2a = 0;

これをしないでください。これは完全に宣言されていない識別子への割り当てです。ルーズモード(ES5以前の唯一のモード)では、グローバルオブジェクトに暗黙的にプロパティを作成します。以前のブログではこれを暗黙のグローバルの恐怖ありがたいことに、彼らはそれを修正しました厳密モードは、ES5 で追加され、新しい種類のスコープ (モジュール内、コンストラクト内など) のデフォルトですclass。厳密モードでは、宣言されていない識別子への割り当てが、常にエラーになるはずだったエラーになります。これは、厳密モードを使用するいくつかの理由の 1 つです。

通常のプロパティを作成するので、それが可能ですdelete

これによって作成されるプロパティは列挙可能です (非常に古い IE8 以前を除く)。


3window.a = 0;またはglobalThis.a = 0;

windowこれは、グローバル (ブラウザ上) またはグローバルオブジェクトを参照するグローバルを使用して、グローバル オブジェクトにプロパティを明示的に作成しますglobalThis。これは通常のプロパティなので、削除できます。

このプロパティは列挙可能です (非常に古い IE8 以前でも)。


4this.a = 0;

3 番とまったく同じですが、グローバル オブジェクトをthisグローバルやではなくwindowで参照していますglobalThis。これは、thisグローバル スコープでは が「グローバル」this値であるため機能します。これは、厳密モードでも当てはまります。(厳密モードでは、を実行した場合など、thisを指定せずに関数を呼び出すときに使用される が変更されますが、グローバル スコープでは は変更されません。) 実際にはグローバルスコープである必要があることに注意してください。 のトップレベル スコープは、thisfn()thisモジュールはグローバルスコープではありません (モジュールスコープです)。モジュールスコープではthisですundefined


プロパティの削除

「削除」または「除去」とはどういう意味ですかa? まさにその通りです: キーワードを使用してプロパティを (完全に) 削除しますdelete:

window.a = 0;
console.log(`"a" in window? ${"a" in window}`); // "a" in window? true
delete window.a;
console.log(`"a" in window? ${"a" in window}`); // "a" in window? false

deletewindowは、オブジェクトからプロパティを完全に削除します。を介して間接的に追加されたプロパティではこれを行うことはできません。varは、delete暗黙的に無視されるか、例外をスローします (厳密モードかどうかによって異なります)。

window(注意: 非常に古い IE8 以前、および壊れた「互換性」モードの古い IE9 ~ IE11 では、プロパティの削除が許可されている場合でも、プロパティの削除はできません。)


いつvar起こるか

前書き: var新しいコードには不要です。代わりにletまたは を使用してください。ただし、遭遇した古いコードを理解する目的でconst理解しておくと便利です。var

ステートメントを介して定義された変数は、実行コンテキスト内のステップバイステップのコードが実行されるvar前に作成されるため、変数 (およびグローバル オブジェクト上のそのプロパティ) はステートメントよりかなり前に存在します。var

これはわかりにくいかもしれませんので、見てみましょう。ここでは、 と にアクセスしようとするコードがありabその後に中間でそれらを作成するコードがあり、その後に再度それらにアクセスしようとするコードがあります。

try {
    console.log(a);   // undefined
    console.log(b);   // ReferenceError: b is not defined
} catch (e) {
    console.error(e);
}

var a = "ayy";
b = "bee";            // Don't do this, but I didn't want to use `let` or `const` in this example

try {
    console.log(a);   // "ayy"
    console.log(b);   // "bee"
} catch (e) {
    console.error(e);
}

ご覧のとおり、識別子は最初の行が実行される前にa定義されています (値 を使用) が、識別子は定義されていないため、その値を読み取ろうとすると になります。この文は実際には、異なるタイミングで2 つの異なることを行います。スコープに入ると、識別子が初期値( 部分) で定義され、その後コードの実行で到達すると、 の値(部分 ) が設定されます。はコードの最初の行が実行される前に定義されているため、これを使用できます (値を参照できます)。これは「巻き上げ」と呼ばれます。これは、部分がグローバル スコープまたは関数スコープの先頭に移動 (「巻き上げ」) されますが、部分は元の場所に残されるためです (undefinedbReferenceErrorvar a = "ayy";undefinedvar aaa = "ayy"aundefinedvarvar aa = "ayy"誤解されたかわいそうなvar私の貧弱な古いブログで。


いつlet、何がconst起こるか

letおよびは、いくつかの便利な点constで とは異なりますvar。質問に関連する点は、A) 定義するバインディングはステップバイステップのコードが実行される前に作成されますが、orステートメントに到達するまでアクセスできないこと、および B) 上で説明したように、グローバル スコープではグローバル オブジェクトにプロパティが作成されないことです。letconst

Re (A) では、次の使用法がvar実行されます。

console.log(a); // undefined
var a = 0;
console.log(a); // 0

この使用法ではletエラーが発生します:

console.log(a); // ReferenceError: a is not defined
let a = 0;
console.log(a);

letconstが と異なる他の 2 つの点 (var質問とは実際には関係ありません) は次のとおりです。

  1. varは常に実行コンテキスト全体に適用されます (グローバル コード全体、またはそれが出現する関数内の関数コード全体。ブロック外にジャンプします)。ただし、 と は、letそれらが出現するブロックconst内にのみ適用されます。つまり、 は関数 (またはグローバル) スコープを持ちますが、と はブロックスコープを持ちます。varletconst

  2. var a同じコンテキスト内での繰り返しは無害ですが、 let a(またはconst a) がある場合、別のlet aまたはconst aまたは があるvar aと構文エラーになります。

次の例は、 と が、そのブロック内のコードが実行される前にブロック内ですぐに有効になりますが、 or ステートメントまでアクセスできないことを示しletconstletますconst

let a = 0;          // (Doesn't matter whether this is `let`, `const`, or `var` [or even `class` or `function`])
console.log(a);     // 0
if (true) {
    console.log(a); // ReferenceError: a is not defined
    let a = 1;
    console.log(a);
}

2 番目の文は、ブロックの外部から にconsole.logアクセスする代わりに、そのブロック内ではブロックの後半で宣言されたを参照するため、失敗することに注意してください。ただし、文はその内部の の Temporal Dead Zone 内で発生するため、エラーが発生します。aaaconsole.loga


グローバルスコープの乱雑さを避ける - モジュールを使用する

グローバル スコープは非常に乱雑です。少なくとも次のようになります。

  • 仕様によって作成された多くのグローバル変数(奇妙なことにキーワードではなくグローバルである や など、さまざまなグローバル関数undefinedNaN
  • (ブラウザ上) を持つすべての DOM 要素と を持つid多くのDOM 要素の変数name( id/name値が有効な識別子である場合。そうでない場合は、 のプロパティでありwindow、グローバル変数ではありません)
  • (ブラウザ上) 、、...windowなどの - 固有の変数namelocationself
  • すべてのグローバルスコープvarステートメントの変数
  • すべてのグローバルスコープのlet、、constおよびclassステートメントの変数

これらのグローバル変数はすべて、ブラウザでのこの典型的な例のように、コードと競合する機会に満ちています。

var name = 42;
console.log(name);          // 42 - seems okay, but...
console.log(typeof name);   // ...string?!?!!

なぜ はname文字列なのでしょうか? これは、常に文字列であるウィンドウ オブジェクトの名前windowの のアクセサー プロパティであるためです。(宣言型環境レコードは概念的にオブジェクト環境レコード内にネストされているため、 の同等の は期待どおりに機能し、 で作成されたバインディングはアクセサー プロパティのバインディングをシャドウします。)letnameletname

可能な限り、混乱を招かないようにしてください。代わりにモジュールを使用してください。モジュール内のトップレベルのスコープはモジュール スコープexportであり、グローバル スコープではありません。そのため、モジュール内の他のコードのみがトップレベルの宣言を参照します。およびを介してモジュール間で情報を共有できますimport

モジュールの前は、コードをラップした「スコープ」関数を使用していました。

// From the ES5 era
(function () {
    var a = 0; // `a` is NOT a property of `window` now

    function example() {
        console.log(a);   // Shows "0", because `example` can access `a`
    }
    
    example();
})();

モジュールにより、それは時代遅れになります。

おすすめ記事