JavaScriptの変数のスコープは何ですか?

2009-02-01 javascript function variables scope var

JavaScriptの変数のスコープは何ですか?関数の外側ではなく、内側に同じスコープがありますか?それとも問題ですか?また、グローバルに定義されている場合、変数はどこに保存されますか?

Answers

グローバルに宣言された変数には、グローバルスコープがあります。関数内で宣言された変数は、その関数をスコープとし、同じ名前のグローバル変数をシャドウします。

(私は確かに本物のJavaScriptプログラマは他の回答で指摘することができるようになりますことを、多くの微妙な点があるんです。特に私が出会ったこのページまさにについてはthisいずれかの時点での手段。うまくいけば、 これ以上の紹介リンクはあなたが始めるのに十分ですでも。)

JavaScriptはスコープチェーンを使用して、特定の関数のスコープを確立します。通常、1つのグローバルスコープがあり、定義された各関数には独自のネストされたスコープがあります。別の関数内で定義された関数には、外部関数にリンクされたローカルスコープがあります。スコープを定義するのは、常にソース内の位置です。

スコープチェーンの要素は、基本的には親スコープへのポインターを持つマップです。

変数を解決するとき、JavaScriptは最も内側のスコープから始まり、外側を検索します。

次に例を示します。

<script>

var globalVariable = 7; //==window.globalVariable

function aGlobal( param ) { //==window.aGlobal(); 
                            //param is only accessible in this function
  var scopedToFunction = {
    //can't be accessed outside of this function

    nested : 3 //accessible by: scopedToFunction.nested
  };

  anotherGlobal = {
    //global because there's no `var`
  }; 

}

</script>

クロージャーと、それらを使用してプライベートメンバーにする方法を調査する必要があります。

TLDR

JavaScriptには字句(静的とも呼ばれる)スコープとクロージャーがあります。これは、ソースコードを見ることで識別子のスコープを知ることができることを意味します。

4つのスコープは次のとおりです。

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

グローバルスコープとモジュールスコープの特別な場合を除いて、変数はvar (関数スコープ)、 let (ブロックスコープ)、 const (ブロックスコープ)を使用して宣言されます。他のほとんどの形式の識別子宣言は、strictモードでブロックスコープを持っています。

概観

スコープは、識別子が有効であるコードベースの領域です。

字句環境は、識別子名とそれに関連付けられた値の間のマッピングです。

スコープは、リンクされた字句環境のネストから形成され、ネストの各レベルは、祖先実行コンテキストの字句環境に対応しています。

これらのリンクされた字句環境は、スコープ「チェーン」を形成します。識別子解決は、一致する識別子をこのチェーンに沿って検索するプロセスです。

識別子の解決は、一方向にのみ行われます。つまり、外側です。このようにして、外部の字句環境は内部の字句環境を「見る」ことができません。

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

  1. 識別子の宣言方法
  2. 識別子が宣言された場所
  3. 厳密モード 厳密モード

識別子を宣言する方法のいくつか:

  1. varletconst
  2. 関数パラメーター
  3. Catchブロックのパラメーター
  4. 関数宣言
  5. 名前付き関数式
  6. グローバルオブジェクトの暗黙的に定義されたプロパティ(つまり、非厳密モードでvarがありません)
  7. import
  8. eval

一部の場所識別子は宣言できます。

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

宣言スタイル

var

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

レットアンドコンスト

letおよびconstを使用して宣言された識別子は、グローバルコンテキストで直接宣言されている場合を除いて、ブロックスコープを持ちます。

注: letconstおよびvar はすべて巻き上げられます。つまり、それらの定義の論理的な位置は、それらを包含するスコープ(ブロックまたは関数)の最上位になります。ただし、 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が間接的に使用されている場合は、グローバルオブジェクトのプロパティとして配置されます。

名前xyzは関数f外では意味がないため、以下はReferenceErrorをスローします。

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がスローされy及びzではないため、 xの視認ので、 xブロックによって制約されません。 ifforwhileなどの制御構造の本体を定義するブロックは、同様に動作します。

{
    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

次の例では、 varに関数スコープがあるため、 xはループの外側に表示されます。

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

...この動作のため、ループでvarを使用して宣言された変数を閉じる場合は注意が必要です。ここで宣言されている変数xインスタンスは1つだけであり、論理的にループの外側にあります。

次のプリント5 、5回、次いで印刷5のための第六時間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

xはブロックスコープであるため、以下はundefinedと出力します。コールバックは非同期で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

以下は、 xの可視性がブロックによって制約されていないため、 ReferenceErrorスローしません。ただし、変数が初期化されていないため、 undefinedされます( ifステートメントのため)。

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

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

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

以下は、 xの可視性がブロックによって制約されているため、 ReferenceErrorをスローします。

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

varletまたは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

Catchブロックのパラメーターは、catch-block本文にスコープされます。

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では、スコープは外部参照によって「チェーン」でリンクされた字句環境を介して実装されます。この字句環境のチェーンはスコープチェーンと呼ばれ、識別子の解決は、一致する識別子のチェーン検索することによって行われます。

詳細をご覧ください

「Javascript 1.7」(MozillaのJavascriptへの拡張)では、 letステートメントを使用してブロックスコープ変数を宣言することもできます。

 var a = 4;
 let (a = 3) {
   alert(a); // 3
 }
 alert(a);   // 4

私が理解しているように、重要なのは、Javascriptには、より一般的なCブロックのスコープに対して関数レベルのスコープがあるということです。

これは、この件に関する優れた記事です。

JavaScriptを初めて使用する多くの人は、言語で継承がデフォルトで利用可能であり、関数スコープがこれまでのところ唯一のスコープであることを理解するのに苦労していることがわかりました。昨年の終わりに書いた、JSprettyと呼ばれる美容器の拡張機能を提供しました。機能はコード内の関数スコープを色分けし、常にそのスコープで宣言されたすべての変数に色を関連付けます。あるスコープの色を持つ変数が別のスコープで使用されると、クロージャが視覚的に示されます。

この機能を試してください:

デモを見る:

次の場所でコードを表示します。

現在、この機能は16のネストされた関数の深さのサポートを提供していますが、現在、グローバル変数に色を付けていません。

この奇妙な例を試してください。以下の例では、aが0で初期化された数値の場合、0と1が表示されます。ただし、aはオブジェクトであり、JavaScriptはf1にそのコピーではなくaのポインタを渡します。その結果、両方の時間に同じアラートが表示されます。

var a = new Date();
function f1(b)
{
    b.setDate(b.getDate()+1);
    alert(b.getDate());
}
f1(a);
alert(a.getDate());

1)グローバルスコープ、関数スコープ、およびwithおよびcatchスコープがあります。変数には一般に「ブロック」レベルのスコープはありません-withおよびcatchステートメントはそれらのブロックに名前を追加します。

2)スコープは、関数によってグローバルスコープまでネストされます。

3)プロパティは、プロトタイプチェーンを介して解決されます。 withステートメントは、オブジェクトのプロパティ名をwithブロックで定義された字句スコープに持ち込みます。

編集:ECMAAScript 6(ハーモニー)はletをサポートするように仕様化されており、クロムが「ハーモニー」フラグを許可していることを知っているので、おそらくサポートします。

Letはブロックレベルのスコープをサポートしますが、それを実現するにはキーワードを使用する必要があります。

編集:コメント内のwithおよびcatchステートメントからのベンジャミンの指摘に基づいて、私は投稿を編集し、さらに追加しました。 withステートメントとcatchステートメントの両方で、それぞれのブロックに変数が導入されます。これブロックスコープです。これらの変数は、渡されたオブジェクトのプロパティにエイリアスされます。

 //chrome (v8)

 var a = { 'test1':'test1val' }
 test1   // error not defined
 with (a) { var test1 = 'replaced' }
 test1   // undefined
 a       // a.test1 = 'replaced'

編集:明確な例:

test1のスコープはwithブロックですが、a.test1のエイリアスです。 'Var test1'は、それがaのプロパティでない限り、上位の字句コンテキスト(関数またはグローバル)に新しい変数test1を作成します。

うわぁ! 「with」の使用には注意してください。変数が関数ですでに定義されている場合、varはnoopであるのと同様に、オブジェクトからインポートされた名前に関してもnoopです!すでに定義されている名前に少し手を加えると、これははるかに安全になります。このため個人的には使用しません。

グローバルスコープ:

グローバル変数はグローバルスターとまったく同じです(Jackie Chan、Nelson Mandela)。アプリケーションのどの部分からでも、それらにアクセス(値を取得または設定)できます。グローバルな機能は、グローバルなイベントのようなものです(新年、クリスマス)。アプリケーションのどの部分からでも実行(呼び出し)できます。

//global variable
var a = 2;

//global function
function b(){
   console.log(a);  //access global variable
}

ローカルスコープ:

あなたがアメリカにいるなら、あなたは有名な有名人であるキム・カーダシアンを知っているかもしれません(彼女はどうにかしてタブロイド紙を作ることができました)。しかし、米国外の人々は彼女を認識しません。彼女は彼女の領土に縛られた地元のスターです。

ローカル変数はローカルスターのようなものです。スコープ内でのみアクセス(値の取得または設定)できます。ローカル関数はローカルイベントのようなものです。そのスコープ内でのみ(祝う)実行できます。スコープ外からアクセスしたい場合、参照エラーが発生します

function b(){
   var d = 21; //local variable
   console.log(d);

   function dog(){  console.log(a); }
     dog(); //execute local function
}

 console.log(d); //ReferenceError: dddddd is not defined    

スコープの詳細については、この記事を確認してください

JavaScriptには2つのタイプのスコープしかありません。

  1. グローバルスコープ :グローバルはウィンドウレベルのスコープにすぎません。ここでは、アプリケーション全体に存在する変数です。
  2. 関数スコープvarキーワードを使用して関数内で宣言された変数には、関数スコープがあります。

関数が呼び出されると、変数スコープオブジェクトが作成され(スコープチェーンに含まれ)、JavaScriptの変数が後に続きます。

        a = "global";
         function outer(){ 
              b = "local";
              console.log(a+b); //"globallocal"
         }
outer();

スコープチェーン->

  1. ウィンドウレベル-及びa outer関数は、スコープチェーンの最上位にあります。
  2. 外部関数が新しいvariable scope object (およびスコープチェーンに含まれる)を呼び出し、その中に変数b追加したとき。

変数a必須の場合、最初に最も近い変数スコープを検索し、変数がない場合は変数スコープチェーンの次のオブジェクトに移動します。この場合はウィンドウレベルです。

コードを実行します。これがスコープについてのアイデアを与えることを願っています

Name = 'global data';
document.Name = 'current document data';
(function(window,document){
var Name = 'local data';
var myObj = {
    Name: 'object data',
    f: function(){
        alert(this.Name);
    }
};

myObj.newFun = function(){
    alert(this.Name);
}

function testFun(){
    alert("Window Scope : " + window.Name + 
          "\nLocal Scope : " + Name + 
          "\nObject Scope : " + this.Name + 
          "\nCurrent document Scope : " + document.Name
         );
}


testFun.call(myObj);
})(window,document);

JSには関数スコープのみがあります。スコープをブロックしないでください! 何が吊り上げられているかがわかります。

var global_variable = "global_variable";
var hoisting_variable = "global_hoist";

// Global variables printed
console.log("global_scope: - global_variable: " + global_variable);
console.log("global_scope: - hoisting_variable: " + hoisting_variable);

if (true) {
    // The variable block will be global, on true condition.
    var block = "block";
}
console.log("global_scope: - block: " + block);

function local_function() {
    var local_variable = "local_variable";
    console.log("local_scope: - local_variable: " + local_variable);
    console.log("local_scope: - global_variable: " + global_variable);
    console.log("local_scope: - block: " + block);
    // The hoisting_variable is undefined at the moment.
    console.log("local_scope: - hoisting_variable: " + hoisting_variable);

    var hoisting_variable = "local_hoist";
    // The hoisting_variable is now set as a local one.
    console.log("local_scope: - hoisting_variable: " + hoisting_variable);
}

local_function();

// No variable in a separate function is visible into the global scope.
console.log("global_scope: - local_variable: " + local_variable);

JavaScriptでスコープするというアイデアは、もともとブレンダンアイヒによって設計されたときに、 HyperCardスクリプト言語HyperTalkから生まれました。

この言語では、表示はインデックスカードのスタックと同様に行われました。背景と呼ばれるマスターカードがありました。それは透明で、一番下のカードとして見ることができます。この基本カードのコンテンツは、その上に配置されたカードと共有されました。上に配置された各カードには、前のカードよりも優先される独自のコンテンツがありましたが、必要に応じて前のカードにアクセスできました。

これがまさにJavaScriptスコープシステムの設計方法です。名前が違うだけです。 JavaScriptのカードは、 実行コンテキストECMAと呼ばれます。これらの各コンテキストには、3つの主要な部分があります。変数環境、字句環境、およびthisバインディング。カード参照に戻ると、字句環境には、スタックの下位にある以前のカードのすべてのコンテンツが含まれています。現在のコンテキストはスタックの最上位にあり、そこで宣言されたコンテンツはすべて変数環境に格納されます。名前の衝突が発生した場合は、変数環境が優先されます。

thisバインディングは、それを含むオブジェクトを指します。含まれているオブジェクトがwindowまたはコンストラクター関数である可能性がある宣言された関数など、含まれているオブジェクトを変更せずにスコープまたは実行コンテキストが変わる場合がありwindow

これらの実行コンテキストは、コントロールが転送されるたびに作成されます。コードが実行を開始すると制御が移り、これは主に関数の実行から行われます。

これが技術的な説明です。実際には、JavaScriptでは

  • スコープは技術的には「実行コンテキスト」です
  • コンテキストは、変数が格納される環境のスタックを形成します
  • スタックの一番上が優先されます(一番下がグローバルコンテキストです)。
  • 各関数は実行コンテキストを作成します(ただし、常に新しいthisバインディングではありません)

これをこのページの前の例(5.「クロージャー」)の1つに適用すると、実行コンテキストのスタックをたどることができます。この例では、スタックに3つのコンテキストがあります。これらは、外部コンテキスト、var sixによって呼び出される即時に呼び出される関数内のコンテキスト、およびvar sixの即時に呼び出される関数内で返される関数内のコンテキストによって定義されます。

i )外部コンテキスト。変数環境はa = 1です
ii )IIFEコンテキスト、それはa = 1の字句環境を持っていますが、スタックで優先されるa = 6の可変環境を持っています
iii )返された関数のコンテキスト。字句環境はa = 6であり、呼び出し時にアラートで参照される値です。

ここに画像の説明を入力してください

JavaScriptには2種類のスコープがあります。

  1. グローバルスコープグローバルスコープでアナウンスされる変数は、プログラムのどこでも非常にスムーズに使用できます。例えば:

    var carName = " BMW";
    
    // code here can use carName
    
    function myFunction() {
         // code here can use carName 
    }
    
  2. 関数スコープまたはローカルスコープ :このスコープで宣言された変数は、独自の関数でのみ使用できます。例えば:

    // code here can not use carName
    function myFunction() {
       var carName = "BMW";
       // code here can use carName
    }
    

ALMOSTには2種類のJavaScriptスコープしかありません。

  • 各var宣言のスコープは、最もすぐ外側の関数に関連付けられています
  • var宣言を囲む関数がない場合、それはグローバルスコープです。

したがって、関数以外のブロックは新しいスコープを作成しません。これが、forループがスコープ外の変数を上書きする理由を説明しています。

var i = 10, v = 10;
for (var i = 0; i < 5; i++) { var v = 5; }
console.log(i, v);
// output 5 5

代わりに関数を使用する:

var i = 10, v = 10;
$.each([0, 1, 2, 3, 4], function(i) { var v = 5; });
console.log(i,v);
// output 10 10

最初の例では、ブロックスコープがなかったため、最初に宣言された変数が上書きされました。 2番目の例では、関数が原因で新しいスコープがあったため、最初に宣言された変数はシャドウされ、上書きされませんでした。

JavaScriptのスコープに関しては、次の点を除いて、これでほとんどすべてのことを知る必要があります。

そのため、JavaScriptのスコープは実際には非常に単純ですが、常に直感的であるとは限りません。注意すべきいくつかのこと:

  • var宣言はスコープの最上部まで引き上げられます。これは、var宣言がどこで発生しても、コンパイラーにとってはvar自体が最上位で発生するかのようになります。
  • 同じスコープ内の複数のvar宣言が結合されている

したがって、このコード:

var i = 1;
function abc() {
  i = 2;
  var i = 3;
}
console.log(i);     // outputs 1

以下と同等です。

var i = 1;
function abc() {
  var i;     // var declaration moved to the top of the scope
  i = 2;
  i = 3;     // the assignment stays where it is
}
console.log(i);

これは直感に反するように見えるかもしれませんが、命令型言語デザイナーの観点からは理にかなっています。

古い学校のJavaScript

従来、JavaScriptには実際には2種類のスコープしかありません。

  1. グローバルスコープ :アプリケーションの最初から変数がアプリケーション全体で認識されます(*)
  2. 関数スコープ :変数は、関数の最初から、それらが宣言されている関数内で認識されます(*)

違いを説明する他の多くの回答がすでにあるので、これについては詳しく説明しません。


最新のJavaScript

最新のJavaScript仕様では、3番目のスコープも許可されるようになりました。

  1. ブロックスコープ :識別子は、 宣言されたスコープの先頭から 「既知」ですが、宣言の行の後まで割り当てまたは逆参照(読み取り)できません。この暫定期間は、「一時的なデッドゾーン」と呼ばれます。

ブロックスコープ変数を作成するにはどうすればよいですか?

従来は、次のように変数を作成します。

var myVariable = "Some text";

ブロックスコープ変数は次のように作成されます。

let myVariable = "Some text";

では、機能範囲とブロック範囲の違いは何ですか?

関数スコープとブロックスコープの違いを理解するには、次のコードを検討してください。

// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here

function loop(arr) {
    // i IS known here, but undefined
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( var i = 0; i < arr.length; i++ ) {
        // i IS known here, and has a value
        // j IS NOT known here
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( let j = 0; j < arr.length; j++ ) {
        // i IS known here, and has a value
        // j IS known here, and has a value
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here
}

loop([1,2,3,4]);

for( var k = 0; k < arr.length; k++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS NOT known here
};

for( let l = 0; l < arr.length; l++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS known here, and has a value
};

loop([1,2,3,4]);

// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here

ここで、変数jは最初のforループでのみ認識され、前後では認識されないことがわかります。しかし、変数iは関数全体で知られています。

また、ブロックスコープの変数は、ホイストされていないため、宣言する前に不明であることを考慮してください。また、同じブロック内で同じブロックスコープ変数を再宣言することもできません。これにより、ブロックスコープ変数は、グローバルまたは機能的にスコープされた変数よりもエラーが発生しにくくなります。


今日、ブロックスコープ変数を使用しても安全ですか?

今日使用しても安全かどうかは、環境によって異なります。

  • サーバーサイドJavaScriptコード( Node.js )を記述している場合は、 letステートメントを安全に使用できます。

  • クライアント側のJavaScriptコードを記述していて、ブラウザーベースのトランスパイラー( Traceurbabel-standaloneなど )を使用している場合は、 letステートメントを安全に使用できますが、コードはパフォーマンスに関して最適ではない可能性があります。

  • クライアント側のJavaScriptコードを記述していて、ノードベースのトランスパイラー( traceurシェルスクリプトBabelなど )を使用している場合は、 letステートメントを安全に使用できます。また、ブラウザーはトランスパイルされたコードのみを認識するため、パフォーマンスの低下は制限されます。

  • クライアント側のJavaScriptコードを記述していて、トランスパイラーを使用しない場合は、ブラウザーのサポートを検討する必要があります。

    これらは、 letをまったくサポートしていないブラウザです。

    • Internet Explorer 10以下
    • Firefox 43以下
    • Safari 9以下
    • Androidブラウザー4以下
    • Opera 27以下
    • 40丁目以下
    • Opera MiniBlackberry Browserのすべてのバージョン

ここに画像の説明を入力してください


ブラウザのサポートを追跡する方法

この回答を読んだ時点でletステートメントをサポートしているブラウザーの最新の概要については、 このCan I Useページ」を参照してください


(*)JavaScript変数がホイストされるため、グローバルおよび機能的にスコープされた変数は、宣言される前に初期化して使用できます。これは、宣言が常にスコープの一番上にあることを意味します。

他の答えに追加すると、スコープは宣言されたすべての識別子(変数)のルックアップリストであり、現在実行中のコードからこれらにアクセスする方法に関する一連の厳密なルールを適用します。このルックアップは、LHS(左側)参照である変数への割り当てを目的とする場合と、RHS(右側)参照であるその値を取得するための場合があります。これらのルックアップは、コードのコンパイルおよび実行時にJavaScriptエンジンが内部で実行していることです。

したがって、この観点から、カイルシンプソンによるScopes and Closures電子ブックで見つけた写真が役立つと思います。

画像

彼の電子ブックからの引用:

建物は、プログラムのネストされたスコープルールセットを表しています。最初 建物の床は、現在実行中のスコープを表しています。 君はどこにいても。建物のトップレベルはグローバルスコープです。 LHSとRHSの参照を解決するには、現在のフロアを見て、 見つからない場合は、エレベーターで次の階に移動し、 そこを見て、次に次のように。最上階に着いたら (グローバルスコープ)、探しているものを見つけるか、または しないでください。しかし、あなたは関係なく停止する必要があります。

言及する価値のある注意点の1つは、「最初の一致が見つかるとスコープの検索が停止する」ことです。

この「スコープレベル」の考え方は、ネストされた関数でルックアップされている場合に、新しく作成されたスコープで「this」を変更できる理由を説明しています。 これらのすべての詳細へのリンクは次のとおりです。JavaScriptスコープについて知りたいすべて

私の理解では、3つのスコープがあります。ブロックに関係なく、関数全体で使用できるローカルスコープ。ブロックスコープは、それが使用されたブロック、ステートメント、または式でのみ使用できます。グローバルスコープとローカルスコープは、関数内または外部のキーワード 'var'で示され、ブロックスコープはキーワード 'let'で示されます。

グローバルスコープとローカルスコープしかないと考えている人のために、なぜMozillaがJSのブロックスコープのニュアンスを説明するページ全体を持っているのか説明してください。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

モダンJs、ES6 +、 ' const 'および ' let '

他のほとんどの主要言語と同様に、作成するすべての変数にブロックスコープを使用する必要があります。 var廃止されました 。これにより、コードの安全性と保守性が向上します。

ケースの95%には constを使用する必要があります。変数参照を変更できないようにします。配列、オブジェクト、DOMノードのプロパティは変更される可能性があり、おそらくconst必要があります。

letは、再割り当てが予想される変数に使用する必要があります。これにはforループ内が含まれます。初期化を超えて値を変更する場合は、 let使用してlet

ブロックスコープとは、変数が宣言されている括弧内でのみ変数が利用可能になることを意味します。これは、スコープ内で作成された無名関数を含む内部スコープにまで及びます。

JavaScriptには2種類のスコープがあります。

  • ローカルスコープ
  • グローバルスコープ

Below関数には、ローカルスコープ変数carNameます。また、この変数には関数の外部からアクセスできません。

function myFunction() {
    var carName = "Volvo";
    alert(carName);
    // code here can use carName
}

Below Classには、グローバルスコープ変数carNameます。そして、この変数はクラスのどこからでもアクセスできます。

class {

    var carName = " Volvo";

    // code here can use carName

    function myFunction() {
        alert(carName);
        // code here can use carName 
    }
}

EcmaScript5には主に2つのスコープ、 ローカルスコープグローバルスコープがありますが、EcmaScript6には主に3つのスコープ、ローカルスコープ、グローバルスコープ、およびブロックスコープと呼ばれる新しいスコープがあります

ブロックスコープの例は次のとおりです。

for ( let i = 0; i < 10; i++)
{
 statement1...
statement2...// inside this scope we can access the value of i, if we want to access the value of i outside for loop it will give undefined.
}

ECMAScript 6では、letおよびconstキーワードが導入されました。これらのキーワードは、varキーワードの代わりに使用できます。 varキーワードとは異なり、letおよびconstキーワードは、ブロックステートメント内のローカルスコープの宣言をサポートします。

var x = 10
let y = 10
const z = 10
{
  x = 20
  let y = 20
  const z = 20
  {
    x = 30
    // x is in the global scope because of the 'var' keyword
    let y = 30
    // y is in the local scope because of the 'let' keyword
    const z = 30
    // z is in the local scope because of the 'const' keyword
    console.log(x) // 30
    console.log(y) // 30
    console.log(z) // 30
  }
  console.log(x) // 30
  console.log(y) // 20
  console.log(z) // 20
}

console.log(x) // 30
console.log(y) // 10
console.log(z) // 10

ES5以前:

JavaScriptの変数は、最初( ES6以前)のレキシカル関数スコープでした。レキシカルスコープの用語は、コードを「調べる」ことで変数のスコープを確認できることを意味します。

varキーワードで宣言されたすべての変数は、関数をスコープとしています。ただし、その関数内で他の関数が宣言されている場合、それらの関数は外部関数の変数にアクセスできます。これは、 スコープチェーンと呼ばれます。次のように機能します。

  1. 関数が変数値を解決しようとするとき、関数はまず自身のスコープを調べます。これは関数本体、つまり中括弧{}の間のすべて(このスコープにある他の 関数変数を除く)です。
  2. 関数本体の内部で変数が見つからない場合は、チェーンに上り 、関数が定義されている関数の変数スコープを調べます。これが字句スコープの意味です。この関数が定義されているコードで確認できるため、コードを見るだけでスコープチェーンを特定できます。

例:

// global scope
var foo = 'global';
var bar = 'global';
var foobar = 'global';

function outerFunc () {
 // outerFunc scope
 var foo = 'outerFunc';
 var foobar = 'outerFunc';
 innerFunc();
 
 function innerFunc(){
 // innerFunc scope
  var foo = 'innerFunc';
  console.log(foo);
  console.log(bar);
  console.log(foobar);
  }
}

outerFunc();

変数foobar 、およびfoobarをコンソールに記録しようとすると、次のようになります。

  1. fooをコンソールに記録しようとしますinnerFuncは関数innerFunc自体の中にあります。したがって、fooの値はストリングinnerFunc解決されます。
  2. コンソールにbarを記録しようとしますが、関数innerFunc自体の中にbarが見つかりません。したがって、 スコープチェーン登る必要があります。最初に、関数innerFuncが定義されている外部関数を調べます。これは関数outerFuncです。 outerFuncのスコープでは、文字列 'outerFunc'を保持する変数barを見つけることができます。
  3. foob​​arがinnerFuncに見つかりません。 。したがって、 スコープチェーンをinnerFuncスコープに登る必要があります。ここでも見つかりません。別のレベルをグローバルスコープ (つまり、最も外側のスコープ)に登ります。ここでは、文字列「global」を保持する変数foobarを見つけます。スコープチェーンを上った後で変数が見つからなかった場合、JSエンジンはreferenceErrorをスローします

ES6 (ES 2015)以前:

字句スコープとスコープチェーンの同じ概念がES6も適用されます。ただし、変数を宣言する新しい方法が導入されました。以下があります。

  • let :ブロックスコープ変数を作成します
  • const :初期化する必要があり、再割り当てできないブロックスコープ変数を作成します

varlet / constの最大の違いは、 varが関数スコープであるのに対し、 let / constはブロックスコープであることです。これを説明する例を次に示します。

let letVar = 'global';
var varVar = 'global';

function foo () {
  
  if (true) {
    // this variable declared with let is scoped to the if block, block scoped
    let letVar = 5;
    // this variable declared with let is scoped to the function block, function scoped
    var varVar = 10;
  }
  
  console.log(letVar);
  console.log(varVar);
}


foo();

上記の例では、 let宣言された変数はブロックスコープであるため、letVarは値globalをログに記録します。それらはそれぞれのブロックの外に存在することをやめるので、変数はifブロックの外にはアクセスできません。

フロントエンドのコーダーがよく遭遇する、まだ説明されていない非常に一般的な問題は、HTMLのインラインイベントハンドラーから見えるスコープです。たとえば、

<button onclick="foo()"></button>

on*属性が参照できる変数のスコープは、次のいずれかでなければなりません。

  • グローバル(動作するインラインハンドラーは、ほとんどの場合グローバル変数を参照します)
  • 文書のプロパティ(例えば、 querySelectorを指すようになり、スタンドアロン変数としてdocument.querySelector ;まれ)
  • ハンドラーがアタッチされている要素のプロパティ(上記のように、まれです)

そうしないと、ハンドラーが呼び出されたときにReferenceErrorが発生します。したがって、たとえば、インラインハンドラー window.onloadまたは$(function() { で定義された関数を参照する場合、インラインハンドラーはグローバルスコープの変数のみを参照でき、関数はグローバル:

window.addEventListener('DOMContentLoaded', () => {
  function foo() {
    console.log('foo running');
  }
});
<button onclick="foo()">click</button>

特性document及びハンドラはインラインハンドラが呼び出されているため、また、インラインハンドラ内部スタンドアロン変数として参照することができるに取り付けられている要素のプロパティ2の内部 withブロック 、のための1つのdocument 、要素のための1つ。これらのハンドラー内の変数のスコープチェーンは非常に直感的ではなく、機能するイベントハンドラーはおそらく関数をグローバルにする必要があります(そして不要なグローバル汚染はおそらく回避する必要があります )。

インラインハンドラー内のスコープチェーンは奇妙であり、インラインハンドラーは機能するためにグローバルな汚染を必要とするため、またインラインハンドラーは引数を渡すときに醜い文字列エスケープを必要とする場合があるため、それらを回避する方がおそらく簡単です。代わりに、HTMLマークアップではなく、JavaScriptを使用して( addEventListenerなどを使用して)イベントハンドラーをアタッチします。

function foo() {
  console.log('foo running');
}
document.querySelector('.my-button').addEventListener('click', foo);
<button class="my-button">click</button>


別の注記では、トップレベルで実行される通常の<script>タグとは異なり、ES6モジュール内のコードは独自のプライベートスコープで実行されます。通常の<script>タグの先頭で定義された変数はグローバルなので、次のように他の<script>タグで参照できます。

<script>
const foo = 'foo';
</script>
<script>
console.log(foo);
</script>

ただし、ES6モジュールの最上位グローバルではありません 。 ES6モジュールの上部で宣言された変数は、変数が明示的にexportされない限り、またはグローバルオブジェクトのプロパティに割り当てられない限り、そのモジュール内でのみ表示されます。

<script type="module">
const foo = 'foo';
</script>
<script>
// Can't access foo here, because the other script is a module
console.log(typeof foo);
</script>

ES6モジュールのトップレベルは、通常の<script>トップレベルのIIFEの内部のレベルに似ています。モジュールはグローバルな変数を参照できます。モジュールが明示的に設計されていない限り、モジュール内の何も参照できません。

私は受け入れられた答えが本当に好きですが、これを追加したいと思います:

スコープは、宣言されたすべての識別子(変数)のルックアップリストを収集して維持し、現在実行中のコードからこれらにアクセスする方法に関する一連の厳密なルールを適用します。

スコープは、識別子名で変数を検索するための一連のルールです。

  • 変数が直接のスコープで見つからない場合、エンジンは次の外側のスコープを調べ、見つかるまで、または最も外側の(別名、グローバル)スコープに到達するまで続行します。
  • 変数(識別子)を検索できる場所と方法を決定する一連のルールです。このルックアップは、LHS(左側)参照である変数への割り当てを目的とする場合と、RHS(右側)参照であるその値を取得することを目的とする場合があります。 。
  • LHS参照は、割り当て操作の結果として生じます。スコープ関連の割り当ては、=演算子を使用するか、関数パラメーターに引数を(割り当て)渡すことによって行うことができます。
  • JavaScriptエンジンは、実行する前にまずコードをコンパイルします。その際、var a = 2のようなステートメントを分割します。 2つの別々のステップに:1番目。まず、そのスコープで宣言するvar a。これは、コード実行前の最初に実行されます。 2番目。その後、a = 2で変数(LHS参照)を検索し、見つかった場合は変数に割り当てます。
  • LHSとRHSの両方の参照ルックアップは、現在実行中のスコープから始まり、必要に応じて(つまり、探しているものが見つからない場合)、ネストされたスコープ、つまり1つのスコープ(floor )グローバル(最上階)に到達して停止するまで、一度に識別子を探して停止し、それを見つけるか、見つけません。 RHS参照が満たされていないと、ReferenceErrorがスローされます。満たされていないLHS参照は、自動的に暗黙的に作成されたその名前のグローバル(厳密モードでない場合)、またはReferenceError(厳密モードの場合)になります。
  • スコープは、それぞれが識別子(変数、関数)が宣言されているコンテナまたはバケットとして機能する一連の「バブル」で構成されます。これらのバブルは互いにきれいに入れ子になっており、この入れ子は作成者によって定義されています。

Related