English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

JS中的作用域と変数昇格(hoisting)の深い理解

スコープ(Scoping)

JavaScriptの初心者にとって最も混乱する点はスコープです。実際には、初心者だけでなく、経験のあるJavaScriptプログラマーも見かけますが、彼らはスコープの理解が浅いです。JavaScriptのスコープが混乱する理由は、そのプログラムの構文がC系言語に似ているからです。私のスコープに対する理解は、特定の範囲に対してのみ作用し、外部に影響を与えない閉じた空間です。そのような空間では、外部から内部の変数にアクセスすることはできませんが、内部から外部の変数にアクセスすることはできます。

C言語の変数はグローバル変数とローカル変数に分けられます。グローバル変数の作用範囲は、どんなファイルや関数でもアクセス可能です(もちろん、変数定義以外のCファイルでは、externキーワードを使用して宣言する必要があります。staticキーワードを使用することでも、作用範囲を現在のファイルに限定することができます)。ローカル変数の作用範囲は、宣言から最も近い大括号がカバーするブロックレベルの範囲です。Javaではグローバル変数はありませんが、クラス変数、メンバ変数、ローカル変数があります。作用範囲はpublic、protected、privateなどのアクセス権限によって異なりますが、ここでは詳しく説明しません。

JSのスコープにはどのようなものがありますか?

在ES5中で、JSにはスコープの形式が2種類あります:グローバルスコープと関数スコープ。

グローバルスコープは実際にはグローバルオブジェクトのスコープであり、どこでもアクセスできます(関数スコープで覆われていない場合)。

関数オブジェクトのスコープはCのローカル変数のスコープとは異なり、関数の全体のスコープです。関数のどこで宣言されたかにかかわらず!これは「昇格」と呼ばれ、変数の昇格の概念です。ただし、急いでいないので、以下でhoistingに特別に説明します。

ただし、ES6中で、新しいブロックレベルのスコープが追加されました(最も近い括弧でカバーされる範囲)、ただしletで宣言された変数に限ります。
スコープのデモ:

変数を定義する際に、varを書かない場合(例えばi=0)、それがグローバル変数として定義され、スコープはグローバルスコープです。または、それ以外の場合はローカル変数として定義され、スコープは関数スコープです。上の第一行のvar i=0がグローバル変数である理由は、それはすでにグローバル領域で宣言されているからです。関数の範囲内ではありません。したがって、i=0と同じです。

そのため、結果がどうなるのか、以下に続いて説明します。

宣言形式

変数宣言:

関数宣言:

変数昇格(Hoisting)

一つの問題を提起します

以下のコードは何を出力しますか?

この問題で多くの人に面接を受けましたが、ほとんどの人は出力が日付であると言いました。しかし実際の結果はundefinedです。なぜこんな結果になるのでしょうか?ここで一つの概念が提起されます--昇格、中国語の意味は変数の昇格です。MDNにおける変数昇格の説明は以下の通りです:

var昇格

変数の宣言(および宣言全般)はコードの実行前に処理されるため、コードのどこかに変数を宣言することは、上に宣言することに等しいです。これはまた、変数が宣言される前に使用されているように見えることも意味します。この動作は「昇格」と呼ばれ、変数の宣言が関数またはグローバルコードの先頭に移動するかのように見えます。

この文章を翻訳すると、以下のようになります:}}

変数の宣言はコードの実行前に処理されるため、コードエリアのどこででも変数を宣言することと一番上(一番上)で宣言することは同じです。つまり、変数が宣言される前に使用できるように見えるという行為は「hoisting」と呼ばれ、変数の宣言が自動的に関数やグローバルコードの一番上に移動するように見えます。

注意:宣言は上昇しますが、定義は上昇しません。

そのため、上記のコードは以下の形式です:

したがって、console出力のとき、tmp変数は宣言はされていますが未定義であり、undefinedが表示されるべきです。

ここで説明すべきことは、すべての宣言(ES5のvar、function、ES6のfunction *、let、const、class)は上昇しますが、var、function、function *とlet、const、classの上昇は異なります!具体的な理由は以下を参照してください。この説明(一般的にはlet、const、classも上昇しますが、初期化されません。このため、参照エラーが発生します。彼らはステートメントの実行時まで初期化されません。初期化される前にの状態はtemporal dead zoneと呼ばれます。以下のコードを見てください):


ここではaは上昇していますが、定義が後であったため、undefinedが表示されます

ここではaは上昇していますが、参照エラーが発生しています!

なぜそのように

このため、変数の宣言の際には、使用する変数をスコープ(グローバルスコープまたは関数スコープ)の一番上に書くことを推奨します。これにより、コードがよりわかりやすくなり、関数スコープからの変数とスコープチェーン(本文では詳しく説明しませんが、読者は百度で検索してください、機会があれば追加説明します)からの変数が区別しやすくなります。

重複宣言

の出力は以下の通りです:1 2 2。中間にxが宣言されているように見えるかもしれませんが、先ほど述べたように、JavaScriptのvar変数はグローバルスコープと関数スコープの2種類しかありませんし、宣言は上昇しますので、実際にはxは一番上の最初の場所で一度だけ宣言されています。var x=2の宣言は無視され、代入にのみ使用されます。つまり、上記のコードは実際には以下のものと同じです。

関数と変数の引き上げの問題

関数と変数の型が同時に宣言定義された場合、何が起こるのでしょうか?以下のコードを見てください。


A

上記の出力結果は実際にはfunction foo(){}、つまり関数の内容です。

そして、以下の形式の場合はどうでしょうか?


B

その出力はundefinedに変わります:

なぜこんなことになるのでしょうか?

関数の引き上げは以下の2つのケースに分類されます:

      一つ:関数宣言。これは上記のA、function foo(){}という形式です。

     もう一つ:関数式。これは上記のB、var foo=function(){}という形式です。

第二种形式は実際にはvar変数の宣言定義です。したがって、上記のBの出力結果がundefinedである理由が理解できるでしょう。

そして、最初の関数宣言の形式は、引き上げる際に全体が引き上げられます。関数定義の部分も含まれます。したがって、Aと以下の形式は等価です!

その理由は:1、関数宣言は一番上に引き上げられます;2、宣言は一度だけ行われ、そのためvar foo='i am text'の宣言は無視されます。

関数宣言の優先順位が変数宣言よりも高いため、以下の形式の出力も関数の内容です:

まとめ

JSのスコープとHoistingを完全に理解するには、以下の3点を覚えておくだけで十分です:

      1、すべての宣言はスコープの一番上に引き上げられます。

      2、同じ変数宣言は一度だけ行われ、そのため他の宣言はすべて無視されます。

      3、関数宣言の優先順位は変数宣言よりも高く、関数宣言は定義とともに引き上げられます。

注意:

with文を使うと、一時的に実行時のスコープチェーンを変更できます。この場合、varで定義されていない変数にアクセスするときは、まずwith中のオブジェクトの属性にアクセスし、その次にスコープチェーンを上方向に確認します。

これでこの記事のすべてです。この記事の内容が皆さんの学習や仕事に役立つことを願っています。何か疑問があれば、コメントを残してください。

おすすめ