ホームページ >ウェブフロントエンド >jsチュートリアル >JS のスコープと変数ホイスティングについての深い理解
スコープ
Javascript 初心者にとって、最も混乱するものの 1 つはスコープです。実際、これは初心者だけではありません。私は何人かの経験豊富な JavaScript プログラマーに会ったことがありますが、彼らはスコープについて深く理解していません。 JavaScript スコープがわかりにくい理由は、そのプログラム構文自体が C 言語ファミリーに似ているためです。私の理解ではスコープとは、一定の範囲のみに影響を与え、外の世界には影響を与えない閉じられた空間のことです。このような空間では、内部変数には外部からアクセスできないが、外部変数には内部からアクセスできる場合があります。
C 言語の変数は、グローバル変数とローカル変数に分けられます。グローバル変数のスコープには、任意のファイルおよび関数からアクセスできます (もちろん、変数以外で定義された他の C ファイルの場合は、宣言に extern キーワードを使用する必要があります)。 、および static キーワードも使用できます。スコープを現在のファイルに制限できます。ローカル変数のスコープは、宣言から最も近い中括弧までのブロックレベルのスコープです。 Java にはグローバル変数はありませんが、クラス変数、メンバー変数、ローカル変数があり、スコープのスコープはパブリック、プロテクト、プライベートなどのアクセス権に応じて異なります。ここでは詳しく説明しません。
JS のスコープは何ですか?
ES5 では、js にはグローバル スコープと関数スコープの 2 つの形式のスコープしかありません。
グローバル スコープは実際にはグローバル オブジェクトのスコープであり、どこからでもアクセスできます (関数スコープでカバーされていない場合)。
関数オブジェクトのスコープは、c のローカル変数のスコープとは異なります。そのスコープは、関数内のどこかで宣言されているかどうかに関係なく、関数のスコープ全体です。これはホイスティングと呼ばれ、変数ホイスティングの概念です。ただし、巻き上げについては以下で具体的に説明しますので、ご安心ください。
ただし、ES6 では、新しいブロックレベルのスコープ (最近の中括弧でカバーされるスコープ) がありますが、それは let メソッドで宣言された変数に限定されます。
スコープのデモ:
変数を定義する際、i=0 のように var を書かなければグローバル変数として定義され、そのスコープはグローバルスコープになります。はローカル変数であり、そのスコープは関数になります。上の最初の行の var i=0 は、グローバル領域で宣言されており、関数のスコープ内にないため、グローバル変数と言われます。つまり、i=0 と同じです。
なぜこのような結果になるのかについては、読み進めていただければわかります。
宣言フォーム
変数宣言:
関数宣言:
変数の巻き上げ(ホイスティング)
という疑問が生まれます
次のコードは何を出力しますか?
この質問のために多くの人にインタビューしましたが、ほとんどの人がアウトプットは日付だと言いました。しかし、実際の結果は未定義です。どうしてこれなの?ここにホイスティングという概念が登場します。これは中国語で可変ホイスティングを意味します。 MDN における変数ホイスティングの説明は次のとおりです:
var hoisting
変数宣言 (および宣言一般) はコードが実行される前に処理されるため、コード内の任意の場所で変数を宣言することは、先頭で変数を宣言することと同じです。また、変数が宣言される前に使用されているように見えることも意味します。この動作は、変数宣言が関数またはグローバル コードの先頭に移動されるように見えるため、「巻き上げ」と呼ばれます。
この部分は翻訳されています。変数宣言は、コードが実行される前に処理されます。コード領域内の任意の場所で変数を宣言することは、先頭 (先頭) で宣言することと同じです。つまり、変数を宣言する前でも使用できるようです。この動作は「ホイスティング」、つまり変数のホイスティングと呼ばれ、変数の宣言が関数またはグローバル コードの先頭に自動的に移動されるように見えます。
注: 宣言のみがアップグレードされ、定義はアップグレードされません。
つまり、上記のコードは実際には次の形式になります:
したがって、コンソール出力時には、tmp 変数は宣言されているだけで定義されていないため、出力は未定義である必要があることを理解する必要があります。
ここで説明する必要があるのは、すべての宣言 (ES5 の var、function、ES6 の function *、let、const、class を含む) は昇格されますが、var、function、function * と let、const、class は改善ではないということです。同じ!具体的な理由はここにあります (一般的な意味は、let、const、class もプロモートされますが、初期化されないということです。この時点でこれらにアクセスすると、ReferenceError 例外が報告されます。これらは待機する必要があります。ステートメントが実行されると初期化され、初期化前の状態は一時的なデッド ゾーンと呼ばれます)。コードの一部を見て確認してみましょう:
ここでは a がプロモートされていますが、後で定義されるため、出力は未定義です
ここでは a がプロモートされていますが、参照エラーが報告されます。
その理由はこれです
このため、変数宣言時にすべての変数をスコープ(グローバルスコープまたは関数スコープ)の先頭に記述することをお勧めします。こうすることで、コードがより明確になります。 , どの変数が関数スコープから来ていて、どの変数がスコープチェーンから来ているかがわかりやすくなります(この記事ではこれ以上説明しません。機会があれば自分で読んで説明を追加してください)。
Repeat ステートメント
上記の出力は実際には: 1 2 2 です。 xが2回宣言されているように見えますが、前述したようにjsのvar変数はグローバルスコープと関数スコープの2種類しかなく、宣言が昇格するため、実際にはxは先頭で1度だけ宣言されます。の場合、var x=2 の宣言は無視され、代入にのみ使用されます。つまり、上記のコードは実際には次のコードと一致しています。
関数と変数の同時昇格の問題
関数と変数の型を同時に宣言、定義するとどうなるでしょうか?以下のコードを見てください
上記の出力は実際には次のとおりです: function foo(){}、これは関数の内容です。
そして、それがこの形式の場合はどうなるでしょうか
出力は次のようになります: unknown
これはなぜでしょうか?
機能のプロモーションは 2 つの状況に分けられることがわかりました:
。それは上記の形式 A、関数 foo(){}
、そしてもう 1 つの関数式です。上記の形式 B、var foo=function(){}
2 番目の形式は、実際には var 変数の宣言定義であるため、上記 B の出力結果が未定義であることは理解できるはずです。
関数宣言の最初の形式は、アップグレードされると、関数定義部分も含めて完全にアップグレードされます。したがって、A は次のメソッドと同等です。
その理由は次のとおりです: 1. 関数の宣言が先頭に昇格されます。 2. 宣言は 1 回だけ行われるため、その後の var foo='i am text' の宣言は無視されます。
そして、関数宣言は変数宣言よりも優先されるため、次の形式の出力も関数の内容になります:
概要
JS のスコープとホイスティングを完全に理解するには、次の 3 つの点を覚えておいてください。できること:
1. すべての宣言はスコープの先頭に昇格されます 2. 同じ変数宣言は 1 回だけ行われるため、他の宣言は無視されます 3. 関数宣言は変数宣言よりも優先され、関数宣言は定義とともに昇格されます
注:
ランタイム コンテキストのスコープ チェーンは、with ステートメントを通じて一時的に変更できます。このとき、var で定義されていない変数にアクセスする場合、with 内のオブジェクトが最初にアクセスされます。プロパティを選択すると、そのプロパティがスコープ チェーンでチェックアップされます。