[Nov.04020]Scope(有効範囲)


有効範囲(Scope)


Scopeの定義


Scope(有効範囲、scope)はjavascript(以下「js」と略す)を含むすべてのプログラミング言語の基本概念である.
次の例を見て、結果値を考えてみましょう.
var x = 'global';

function foo () {
  var x = 'function scope';
  console.log(x);
}

foo(); // ?
console.log(x); // ?
同じ名前の変数xを繰り返し宣言します.
グローバル(global)で変数xを参照する場合、関数fooで変数xを参照する場合、名前が重複する2つの変数の中でどの変数を参照しますか?jsはどのように変数を識別しますか?
scopeは、関数の名前などの参照オブジェクト識別子(identifier==変数)を検索するためのルールで、あるオブジェクトを他のオブジェクトと区別してオブジェクトを識別できます.jsはこのルールに従って識別子を検索します.
すなわち,スキャナは識別子名の競合を防止する役割を果たす.
変数宣言は、グローバル、コードブロック(code block=if、for、while、try/catchなど)または関数内で、コードブロックまたは関数をネストできます.

Scopeの区分


jsはスキャンを2つに分けます.
1.グローバルレンジ(グローバルスキャン)
2.ローカル範囲|機能レベル範囲(ゾーンスキャン)
各変数にはScopeが1つあるので、変数の角度からScopeを区別します
1.グローバル変数(グローバル変数)
グローバル変数は、コードのどこでも参照できます.
2.ローカル変数(ゾーン変数)
領域(関数)内で宣言される変数は、領域(関数)と下位層(関数=Nested function)でのみ参照できます.

JavaScriptスキャンプロパティ


jsのscopeは他の言語とは異なる特徴を持っている.
上でlocalscopeをFunction-level scopeと呼ぶのは、
ほとんどのC-family言語はブロックレベルの範囲(block-level scope)に従います.
ブロックレベルスキャンコードブロック({...})内部有効スキャンを表します.
ここで「有効」とは「参照できる」という意味です.
ただし、jsは関数レベルの範囲(Function-level scope)に従います.
この言葉がコードブロック({...})すなわち,中で宣言してもその変数はグローバル変数となる.
ただし、ECMAScript 6(以下、「ES 6」と略す)に導入されたletキーにより、ブロックレベルスキャンを用いることができる.
var x = 0;
{
  var x = 1;
  console.log(x); // 1
}
console.log(x);   // 1

let y = 0;
{
  let y = 1;
  console.log(y); // 1
}
console.log(y);   // 0

グローバルレンジ


グローバルで変数を宣言すると、その変数は、任意の場所で参照できるグローバルスキャンを持つグローバル変数になります.
varキーワードとして宣言されるグローバル変数は、グローバルオブジェクトwindowpropertyです.
オブジェクト(object)の値をプロパティ(property)と呼び、関数の値をメソッド(method)と呼びます.[image]
var global = 'global';

function foo() {
  var local = 'local';
  console.log(global);
  console.log(local);
}
foo(); // 'global', 'local'

console.log(global); // 'global'
console.log(local); // Uncaught ReferenceError: local is not defined
jsは他の言語と異なり,特殊な開始点(Entry point)がないため,上記のコードのように変数や関数をグローバルに宣言しやすい.
しかし,これはグローバル変数の乱用により様々な問題を引き起こす可能性が高い.
グローバル変数の使用は変数名を繰り返す可能性がありますが、予期せぬ再割り当てによるステータスの変化によってコードが予測しにくい使用は使用しないほうがいいです.

ノンブロックスキャン

if (true) {
  var x = 5;
}
console.log(x); // 5
変数xは、コードブロック内で宣言される.しかしながら、jsは基本的に関数レベルスキャンを有するため、このような宣言がブロック内で宣言されても変数xはグローバル変数となる.
これを防止するには、ES 6に導入されたletキーワードを使用します.

かんすうレベルそうさ

var a = 10;     // global variable

(function () {
  var b = 20;   // local variable
})();

console.log(a); // 10
console.log(b); // "b" is not defined
上述したように、直ちに関数を実行する場合は、即時実行関数(IIFE==即時呼び出しの関数式)と呼ばれ、関数が実行されると直ちにグローバルから消滅するという特徴がある.
jsは関数レベルスキャンを使用するため、関数で宣言されるパラメータ(パラメータ)と変数は関数内部でのみ有効です.すなわち、変数bは領域変数である.
関数にはパラメータ(parameter)と伝達パラメータ(paramet==パラメータ)があります.
中学校数学を例にとると、式がf(x) = x * xである場合、パラメータはxになり、f(3)などの関数呼び出し部分の値3がその関数の伝達パラメータになる.
var x = 'global';

function foo() {
  var x = 'local';
  console.log(x);
}

foo();          // local
console.log(x); // global
グローバル変数xと領域変数xは繰り返し宣言される.グローバル領域では、グローバル変数のみが参照でき、関数内の領域ではグローバル変数と領域変数の両方が参照できますが、変数名が重複している場合(上記の例のように)、領域変数が優先的に参照されます.
これは,インポート部のコードの結果値がそれぞれ「functionscope」と「global」であることを示している.
関数に関数が存在する場合(内部関数):
var x = 'global';

function foo() {
  var x = 'local';
  console.log(x);

  function bar() {  // 내부 함수
    console.log(x); // ?
  }

  bar();
}
foo(); // ?
console.log(x); // ?
内部関数は、独自の外部関数を含む変数にアクセスできます.클로저(closure)に示すように、内部関数の生存時間が長くなると、他の言語とは異なる動作が発生します.
エンクロージャの場合は、後で位置決めしてリンクを掛けます.함수 barで参照される変数xは、함수 fooで宣言される領域変数である.これは、グローバル変数xが실행 컨텍스트(execution context)の範囲チェーン(scope chain)によって参照順序から後回しにされるためである.
「≪実行コンテキスト|Execute Contexts|emdw≫」では、後でリンクを追加するための位置決めが行われます.
var x = 10;

function foo() {
  x = 100;
  console.log(x);
}
foo(); // ?
console.log(x); // ?
グローバル変数は、関数(領域)領域で参照できるため、グローバル変数の値を変更することもできます.
関数内で宣言された変数をスキップすると、グローバル変数varになります.
関数に関数(内部関数)が含まれている場合は、親関数の変数に할당/재할당を設定できません.
重なり合うスペクトルは、最も隣接する領域を優先的に参照する.
var foo = function ( ) {

  var a = 3, b = 5;

  var bar = function ( ) {

    var b = 7, c = 11; // 이 시점에서 a = 3, b = 7, c = 11

    a += b + c; // 이 시점에서 a = 21, b = 7, c = 11

    console.log(a, b, c);

  }; // 이 시점에서 a = 3, b = 5, c = not defined

  bar( ); // 이 시점에서 a = 21, b = 5

  console.log(a, b);

};

foo(); // 21, 7, 11 && 21, 5
function funcName(){...} == var funcName = function() {...}

LexicalScope(LexicalScope)


次の例の実行結果を考慮します.
var x = 1;

function foo() {
  var x = 10;
  bar();
}

function bar() {
  console.log(x);
}

foo(); // ?
bar(); // ?
上記の例の実行結果は、할당/재할당の親スキャンが何であるかによって異なります.
ここでは2つのパターンを予測できる.
  • 함수 bar=ダイナミックレンジ
  • 함수를 어디서 호출親スキャンの有無に応じて==静的スキャン(static scope)|Lexical scope(語彙scope)

  • jsとほとんどの言語はLexical Scopeに従います.
    したがって、함수를 어디서 선언の親スキャンプログラムは함수 barである.
    このことから、上記結果値はvar x = 1foo(); == 1であることがわかる.

    デフォルトグローバル(Implicit Global)

    var x = 10; // 전역 변수
    
    function foo () {
      // 선언하지 않은 식별자
      y = 20;
      console.log(x + y);
    }
    
    foo(); // 30
    上記の例では、yは宣言されていない識別子である.そのため、エラーが発生する可能性があります.
    yの動作は宣言された変数のようです.これは、宣言されていない識別子に値を指定すると、その値がグローバルオブジェクトのプロパティになるためです.
    関数fooが呼び出されると、jsエンジンは、まず、変数yに値を付与するために変数yを宣言するか否かをスキャンチェーンによって決定する.
    このとき、関数fooのscopeとグローバルscopeは、変数yの宣言がどこにも見つからないため、jsエンジンはbar(); == 1y = 20と解釈してpropertyを動的に生成する.
    この現象をwindow.y = 20と呼ぶ.
    しかしyは変数を宣言せず,グローバルオブジェクトのpropertyとして追加しただけである.
    したがって、yは変数ではなく、非変数yは암묵적 전역(implicit global)発生しない.
    ヘイズ亭については、後で転送してリンクを追加します.
    // 전역 변수 x는 호이스팅이 발생한다.
    console.log(x); // undefined
    // 전역 변수가 아니라 단지 전역 property인 y는 호이스팅이 발생하지 않는다.
    console.log(y); // ReferenceError: y is not defined
    
    var x = 10; // 전역 변수
    
    function foo () {
      // 선언하지 않은 변수
      y = 20;
      console.log(x + y);
    }
    
    foo(); // 30
    また、属性y(変数ではなく)を변수 호이스팅(variable hoisting)演算子として削除することができる.
    グローバル変数は属性ですが、演算子deleteでは削除できません.
    var x = 10; // 전역 변수
    
    function foo () {
      // 선언하지 않은 변수
      y = 20;
      console.log(x + y);
    }
    
    foo(); // 30
    
    console.log(window.x); // 10
    console.log(window.y); // 20
    
    delete x; // 전역 변수는 삭제되지 않는다.
    delete y; // 프로퍼티는 삭제된다.
    
    console.log(window.x); // 10
    console.log(window.y); // undefined

    最小のグローバル変数の使用


    グローバル変数を最小化する方法の1つは、アプリケーションでグローバル変数を使用するためにグローバル変数オブジェクトを作成して使用することです.(Douglas Crockfordはjs開発に参加することを提案した)
    オブジェクトの場合は、後でナビゲートしてリンクを追加します.
    var MYAPP = {};
    
    MYAPP.student = {
      name: 'Lee',
      gender: 'male'
    };
    
    console.log(MYAPP.student.name); // 'Lee'

    即時実行関数(IIFE==即時呼び出し関数式)の使用を禁止するグローバル変数


    グローバル変数の使用を抑制するには、インスタント実行関数を使用します.
    このメソッドはグローバル変数を作成しないため、ライブラリでよく使用されます.
    関数はすぐに実行され、すぐにグローバルから消えます.
    ライブラリの場合、後でフレームワーク(framework)を使用してリンクを配置します.
    (function () {
      var MYAPP = {};
    
      MYAPP.student = {
        name: 'Lee',
        gender: 'male'
      };
    
      console.log(MYAPP.student.name);
    }()); // 실행이 되면서 사라
    
    console.log(MYAPP.student.name); // 'Lee'
    Reference
  • https://poiemaweb.com/js-scope#3-function-level-scope
  • JavaScript : The Good Parts 03.Object -by Douglas Crockford