blog

JSスコープ、実行コンテキスト、クロージャ

スコープとは、コードの実行中に変数や関数にアクセスできる範囲のことです。たとえば、共有で貸し出す部屋は、共有スペースといくつかの個室に分かれています。共有スペースにはすべてのテナントがアクセスできます...

Jan 23, 2020 · 8 min. read
シェア

スコープ

概念

スコープとは、コードの実行時に変数や関数にアクセスできる範囲のことです。例えば、共同で使用するために貸し出されている家は、すべてのテナントがアクセスできる共有エリアと、属するテナントだけがアクセスできる個々の部屋に分かれています。変数や関数も同様で、どの範囲のコードでもアクセスできるものと、特定の範囲のコードでしかアクセスできないものがあります。

分類

スコープは以下のカテゴリーに分けられます:

  • グローバル・スコープ:グローバル・スコープを持つ変数をグローバル変数と呼びます。
  • ローカル・スコープ: ローカル・スコープを持つ変数をローカル変数と呼びます。ローカルスコープはさらに以下の2つに分類されます:
    • 関数スコープ
    • ブロックレベルのスコープ

適用ルール

スコープへのアクセスおよび適用可能性については、以下をご参照ください:

// グローバルスコープ
// アクセス可能性:コードのどのスコープからもアクセスできる。
// アプリケーション
// 1.最も外側のレベルで宣言された変数と関数、例えばaやbar
// 2.宣言なしで直接値が代入される変数、例えばb
var a = 1;
function bar() {
 b = 2;
 console.log(a, b);
 console.log(b);
}
bar(); // 1, 2
console.log(a); // 1
console.log(b); // 2
// 関数スコープ
// アクセス可能性:現在の関数内部のコードからのみアクセスできる。
// 適用可能なケース:関数内部で宣言された変数、例えばbarのaやb、fooのa
function bar() {
 var a = 1;
 var b = 2;
 console.log(a, b);
}
function foo() {
 var a = 3;
 console.log(a);
 console.log(b);
}
bar(); // 1, 2
foo(); // 3, b is not defined
console.log(a); // a is not defined
console.log(b); // b is not defined
// ブロックレベルのスコープ
// アクセス可能性:現在のコードブロック内のコードからのみアクセスできる。
// 該当するケース:コードブロックの内部で、constやletを使って宣言された変数、例えばj
for (var i = 0; i < 1; i++) {
 console.log(i);
 console.log(j); // j is not defined
}
for (let j = 0; j < 1; j++) {
 console.log(i);
 console.log(j);
}
console.log(i);
console.log(j); // j is not defined

スコープチェーン

スコープは階層的です:

function a() {
    function c() {
    }
}

内側のスコープは外側のスコープにアクセスでき、内側のスコープから外側のスコープへ変数を検索して形成される連鎖をスコープ・チェーンと呼びます。グローバルスコープは最も外側のスコープであり、このためグローバル変数はどの範囲のコードからもアクセスできます。なお、内側のスコープと外側のスコープに同じ名前の変数がある場合、外側のスコープではその変数はマスクされます。以下を参照してください:

var foo = 'global';
function obj() {
    var bar = 'outer';
    function c() {
        var Obj = 'inner';
        console.log(foo, bar, Obj);
    }
    c();
}
obj();
var foo = 'global';
function obj() {
    var A = 'outer';
    function B() {
        var Foo = 'inner';
        console.log(Foo);
    }
    B();
    console.log(A);
}
obj();
console.log(foo);

静的スコープ

JSでは、変数が持つスコープの種類はコードが書かれた時点で決まります。このようにして決定されるスコープを静的スコープと呼びます。その反対は動的スコープで、変数が持つスコープの種類はコードの実行時に決定されます。両者の違いを以下に示します:

var b = 'global';
function obj() {
    console.log(b);
}
function c() {
    var a = 'bar';
    obj();
}
c();

実行コンテキスト

概念

コードが実行される環境をランタイム環境と呼びます。しかし、抽象的な概念である実行環境は、他のものによって可視化される必要があります。例えば、ある場所の自然環境のレベルは、「悪い」「平均的」「良い」という言葉で表現すると主観的で抽象的なものになりますがしかし、「一人当たりの緑地面積」や「緑地カバー率」といった指標で分析・評価すると、自然環境のレベルという概念が明確になります。実施コンテクストとは、そのようなものの一つで、実施環境を明確かつ具体的に記述するために使用され、構文的にはオブジェクトとみなすことができます。

分類

動作環境は以下のカテゴリーに分けられます:

  • 関数環境。
  • 関数環境。
  • eval function environment ().

そのため、実行コンテキストは以下のように分類されています:

  • グローバル実行コンテキスト。
  • 関数の実行コンテキスト。
  • eval 関数が実行されるコンテキスト。

eval関数環境とeval関数実行コンテキストは一般的には使用されないので、以下では説明しません。

ライフサイクルと構造

次のコードは、実行コンテキストのライフサイクルとその構造を説明するための例です:

ES3

var Foo = 0x1;
function B(FOo, BAr) {
    var foO = Foo + FOo + BAr;
    console.log(foO);
}
var bAr = 0x2;
var fOo = function obJ() {
};
B(0x3, 0x4);
var oBj = {
    'b': {
        'a': undefined,
        'obj': f(x, y),
        'c': undefined,
        'd': undefined
    },
    'C': [GlobalScope],
    'A': Window
};
oBj = {
    'Obj': {
        'a': 0x1,
        'obj': f(x, y),
        'c': 0x2,
        'd': f()
    },
    'C': [GlobalScope],
    'A': Window
};
var OBj = {
    'b': {
        'x': 0x3,
        'y': 0x4,
        'arguments': { 0x0: 0x3, 0x1: 0x4, 'Bar': f(x, y)
        },
        'b': undefined
    },
    'C': [
        FooFunctionScope,
        GlobalScope
    ],
    'A': Window
};

E5S

ES3にあった変数オブジェクトとアクティビティ・オブジェクトは削除され、ES5の語彙環境コンポーネントと変数環境コンポーネントに置き換えられました。以下を参照してください:

var B = {
    'obj': {
        'a': { 'foo': 'Object', 'a': uninitialized, 'bar': f(x, y)
        },
        'b': null
    },
    'c': {
        'a': { 'foo': 'Object', 'b': undefined
        },
        'b': null
    },
    'A': ''
};
var Obj = {
    'obj': {
        'a': { 'foo': 'Declarative', 'x': 0x3, 'y': 0x4, 'arguments': { 0x0: 0x3, 0x1: 0x4, 'C': f(x, y) }
        },
        'b': B.obj
    },
    'c': {
        'a': { 'foo': 'Declarative', 'b': undefined
        },
        'b': B.c
    },
    'A': ''
};

まとめ

実行コンテキストのライフサイクル:

  • 作成段階:実行環境に入った後。
  • 実行フェーズ: 現在の環境のコードが実行されるとき。
  • 破棄フェーズ:現環境でのコード実行後、現環境を終了した後

それは実行コンテキストの構造に基づいて知られています:

  • 関数と変数のリフティングは、実行コンテキストの作成フェーズで行われます。
  • 関数式、const、let 変数の変数リフティングはありません。
  • 実行コンテキストとは、スコープ・メカニズムが実装されている媒体のことです。

コールスタック

関数が呼び出されると、その関数の実行コンテキストが作成されます。JSでは、これらの実行コンテキストを実行スタックと呼ばれるスタックを使って管理しています。コードの実行開始時にはグローバル実行コンテキストがスタックに置かれ、関数を呼び出す時にはその関数の実行コンテキストがスタックに置かれ、関数の実行が終了した後、すべてのコードの実行が終了するとグローバル実行コンテキストはスタックから外されます。したがって、実行スタックでは、スタックの一番下がグローバル実行コンテキストで、スタックの一番上が現在の実行コンテキストです。

クロージャ

概念

関数の中で宣言された関数はクロージャと呼ばれる。この説明は表面的なもので、スコープや実行コンテキストなしにクロージャを説明することは不可能です。実際には、関数とその外部スコープへの参照が一緒になってクロージャを形成しています。クロージャを通して、関数の外部スコープにある変数にアクセスすることができます。

原理

閉鎖の原則については下記をご参照ください:

function b(c) {
    var obj = 0x0;
    return function Obj() {
        var A = 0x5;
        obj++;
        return c + obj + A;
    };
}
var a = b(0xa);
a();
a();

問題の提起

クロージャは外部スコープへの参照を保持するため、外部スコープが再利用されるのを防ぎ、リソースの浪費を招きます。メモリはクロージャ関数を再参照することで解放できます。

Read next

[翻訳] ウェブRTCウィークリー337号

なぜでしょうね。 顧客層の利用行動の変化をよく分析しています。 このような代替案を推測するのはずっと簡単。 AppleがVP9について言及するのを初めて見ました。 WebRではいつになるんでしょうね...。

Jan 22, 2020 · 1 min read