1. ホーム
  2. javascript

[解決済み] JavaScriptの変数のスコープとは何ですか?

2022-03-15 02:11:32

質問

javascriptの変数のスコープとは何ですか?関数の内部と外部で同じスコープを持つのでしょうか?あるいは、それは重要なことなのでしょうか?また、グローバルに定義されている場合、変数はどこに格納されるのでしょうか?

解決方法は?

TLDR

JavaScript にはレキシカル (静的) スコープとクロージャがあります。これは、ソースコードを見れば、識別子のスコープがわかるということです。

4つのスコープがあります。

  1. グローバル - すべてから見える
  2. 関数 - 関数(およびそのサブ関数とブロック)内で表示されます。
  3. ブロック - ブロック(およびそのサブブロック)内に表示されます。
  4. Module - モジュール内に表示されます。

グローバルスコープとモジュールスコープという特殊なケースを除いて、変数の宣言には var (関数スコープ)。 let (ブロックスコープ)、および const (ブロックスコープ)。その他のほとんどの識別子宣言の形式は、ストリクトモードではブロックスコープを持ちます。

概要

スコープとは、ある識別子が有効なコードベースの領域を指します。

字句環境とは、識別子の名前とそれに関連する値の間のマッピングである。

スコープは、語彙的環境のリンクされた入れ子で形成され、入れ子の各レベルは、祖先の実行コンテキストの語彙的環境に対応する。

これらのリンクされた語彙環境は、スコープチェーンと呼ばれます。識別子の解決は、この連鎖に沿って一致する識別子を探す処理である。

識別子の解決は一方向にのみ行われる。このため、外側の語彙環境から内側の語彙環境を覗き込むことはできない。

を決定する上で、3つの適切な要素があります。 スコープ 識別子 をJavaScriptで作成します。

  1. 識別子の宣言方法
  2. 識別子が宣言された場所
  3. にいるかどうか。 ストリクトモード または 非厳格モード

識別子の宣言方法の一部を紹介します。

  1. var , letconst
  2. 関数パラメータ
  3. キャッチブロックパラメータ
  4. 関数宣言
  5. 名前付き関数式
  6. グローバルオブジェクトに暗黙的に定義されたプロパティ(すなわち、欠落している var 非厳密モード時)
  7. import ステートメント
  8. eval

一部の場所の識別子を宣言することができます。

  1. グローバルコンテキスト
  2. 関数本体
  3. 通常のブロック
  4. 制御構造(ループ、if、whileなど)の先頭。
  5. 制御構造本体
  6. モジュール

宣言のスタイル

変数

を使用して宣言された識別子は var 関数スコープを持つ ただし、グローバルコンテキストで直接宣言された場合は、グローバルオブジェクトのプロパティとして追加され、グローバルスコープを持つようになります。この場合、それらはグローバルオブジェクトのプロパティとして追加され、グローバルスコープを持ちます。 eval 関数を使用することができます。

let と const

を使用して宣言された識別子は letconst ブロックスコープを持つ ただし、グローバルコンテキストで直接宣言された場合は、グローバルスコープを持つ。

let , constvar はすべてホイスト . これは、論理的な定義位置が、そのスコープ(ブロックまたは関数)を囲む最上位にあることを意味します。しかし letconst は、ソースコード上で宣言されたポイントを制御が通過するまで、読み出しや代入ができません。この間の期間を時間的不感地帯といいます。

function f() {
    function g() {
        console.log(x)
    }
    let x = 1
    g()
}
f() // 1 because x is hoisted even though declared with `let`!

関数パラメータ名

関数パラメータ名は、関数本体にスコープされます。これには若干の複雑さがあることに注意してください。デフォルトの引数として宣言された関数は パラメータ一覧 であり、関数本体ではありません。

関数の宣言

関数宣言は、strict モードではブロックスコープ、non-strict モードでは関数スコープを持ちます。注意:非厳密モードは、各ブラウザの歴史的な実装に基づく複雑なルールです。

名前付き関数式

名前付き関数式は自分自身にスコープされます(例:再帰のため)。

グローバルオブジェクトに暗黙的に定義されたプロパティ

非厳密モードでは、グローバルオブジェクトに暗黙的に定義されたプロパティは、グローバルスコープを持ちます。ストリクトモードでは、これらは許可されません。

評価

eval 文字列を使用して宣言された変数は var は現在のスコープに配置され、もし eval が間接的に使われている場合、グローバルオブジェクトのプロパティとして使われます。

以下のようにすると、名前 x , y および z は関数の外では意味を持ちません。 f .

function f() {
    var x = 1
    let y = 1
    const z = 1
}
console.log(typeof x) // undefined (because var has function scope!)
console.log(typeof y) // undefined (because the body of the function is a block)
console.log(typeof z) // undefined (because the body of the function is a block)

に対してReferenceErrorを投げます。 yz には適用されません。 x の可視性が低下するためです。 x はブロックによって制約されない。のような制御構造の本体を定義するブロックは、ブロックの制約を受けない。 if , for および while と同じような動作をします。

{
    var x = 1
    let y = 1
    const z = 1
}
console.log(x) // 1
console.log(typeof y) // undefined because `y` has block scope
console.log(typeof z) // undefined because `z` has block scope

以下では x がループの外側に見えるのは var は関数スコープを持っています。

for(var x = 0; x < 5; ++x) {}
console.log(x) // 5 (note this is outside the loop!)

...このような動作があるため、[ ]で宣言された変数のクローズには注意が必要です。 var をループで使用することができます。変数のインスタンスは1つだけです。 x ここで宣言されているのは、論理的にはループの外側に位置するものです。

次のように印刷されます。 5 を5回表示し、その後 5 に対して6回目の console.log ループの外側で

for(var x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop
}
console.log(x) // note: visible outside the loop

次のように印刷されます。 undefined なぜなら x はブロックスコープされています。コールバックは非同期に1つずつ実行されます。の新しい動作は let という名前の異なる変数を閉じる各匿名関数を意味します。 x (を使用した場合とは異なり var というように、整数 0 を通して 4 が印刷されます。

for(let x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables
}
console.log(typeof x) // undefined

以下は ReferenceError の可視性が低下するため x はブロックの制約を受けません。しかし、このブロックは undefined というのは、変数が初期化されていないためです( if ステートメント)。

if(false) {
    var x = 1
}
console.log(x) // here, `x` has been declared, but not initialised

の先頭で宣言された変数は for を使用したループ let はループの本体にスコープされています。

for(let x = 0; x < 10; ++x) {} 
console.log(typeof x) // undefined, because `x` is block-scoped

以下のようにすると ReferenceError の可視性が低いため x はブロックによって制約されます。

if(false) {
    let x = 1
}
console.log(typeof x) // undefined, because `x` is block-scoped

を使用して宣言された変数です。 var , let または const はすべてモジュールにスコープされています。

// module1.js

var x = 0
export function f() {}

//module2.js

import f from 'module1.js'

console.log(x) // throws ReferenceError

を使用して宣言された変数は、グローバルオブジェクトのプロパティを宣言することになります。 var は、グローバルコンテキストの中で、グローバルオブジェクトのプロパティとして追加されます。

var x = 1
console.log(window.hasOwnProperty('x')) // true

letconst の場合、グローバルオブジェクトにプロパティは追加されませんが、グローバルスコープを持ちます。

let x = 1
console.log(window.hasOwnProperty('x')) // false

関数のパラメータは、関数本体で宣言されていると考えてよいでしょう。

function f(x) {}
console.log(typeof x) // undefined, because `x` is scoped to the function

キャッチブロックのパラメータは、キャッチブロック本体にスコープされます。

try {} catch(e) {}
console.log(typeof e) // undefined, because `e` is scoped to the catch block

名前付き関数式は、式そのものにのみスコープが設定されます。

(function foo() { console.log(foo) })()
console.log(typeof foo) // undefined, because `foo` is scoped to its own expression

非厳密モードでは、グローバルオブジェクトに暗黙的に定義されたプロパティは、グローバルにスコープされます。ストリクトモードでは、エラーが発生します。

x = 1 // implicitly defined property on the global object (no "var"!)

console.log(x) // 1
console.log(window.hasOwnProperty('x')) // true

非厳密モードでは、関数宣言は関数スコープを持ちます。厳密モードでは、ブロックスコープを持ちます。

'use strict'
{
    function foo() {}
}
console.log(typeof foo) // undefined, because `foo` is block-scoped

ボンネットの中の仕組み

スコープを定義するのは レキシカル 識別子が有効なコードの領域。

JavaScriptでは、すべての関数オブジェクトに、非表示の [[Environment]] の参照であり、その参照は 字句環境 実行コンテキスト (スタックフレーム) 内で作成されました。

関数を呼び出すと、非表示の [[Call]] メソッドが呼び出されます。このメソッドは新しい実行コンテキストを作成し、新しい実行コンテキストと関数オブジェクトのレキシカル環境との間のリンクを確立します。これは [[Environment]] 関数オブジェクトの値を 外部参照 フィールドを、新しい実行コンテキストの字句環境上に作成します。

なお、この新しい実行コンテキストと関数オブジェクトのレキシカル環境との間のリンクは クロージャ .

このように、JavaScript では、スコープは外部参照によって "chain" にリンクされた lexical 環境によって実装されています。この語彙環境の連鎖をスコープチェーンと呼び、識別子の解決は次のようにして行われます。 チェーンを検索する に一致する識別子を探す。

検索する もっと .