JavaScriptの語法環境とクローズドについて話します.


先輩の同僚が私に面接に行く前に、JSの知識点を聞く時は、積極的にクローズドを言わないでください.穴ですかあ
クローズドは確かにjsの難点と重点です.実はそんなに怖くないです.鍵はメカニズムの理解です.関数と一緒に単独で取り出して話してもいいです.実はクローズドの説明について多くの文章が詳しく書かれています.この文章は自分の学習過程の記録として使います.
クローズドの概念
まず閉包の概念を明確にします.
MDN(Mozilla Develop Network)上の閉塞パケットの定義:
クローズドとは、自由変数にアクセスできる関数です.言い換えれば、クローズド内の関数を定義すると「記憶」が作成されたときの環境を「記憶」することができます.
分析:
  • は、関数とその関連する参照環境(ワード環境)との組み合わせによって、クローズドされて
  • になる.
  • クローズドは、関数がその参照環境(ワード環境)内の変数(フリー変数とも呼ばれる)にアクセスすることを可能にする
  • .
  • 広義的には、すべてのJSの関数をクローズドと呼ぶことができます.JS関数は、作成時に現在のロケーション
  • を保存しています.
    やはり言いにくいのは木があって、1つの面がぼんやりしている時基礎の概念から探すべきで、だから私達は語法の環境を話しにきます.
    語法環境の概念
    定義(ウィキペディア).
    語法環境は、特定の変数と関数識別子を定義するためのECMAScriptコードの語法ネスト構造上の関連関係の仕様タイプです.一つの品詞環境は環境記録項目と空かもしれない外部品詞環境引用から構成されています.
    変数のスコープ
    一般的に、プログラミング言語には変数のスコープという概念があります.各変数は自分のライフサイクルとスコープがあります.作用領域には二つの解析方法があります.
  • 静的作用領域はまた語法作用領域と呼ばれ、コンパイル次数で変数の参照が決定され、プログラムによって定義された位置によって決定され、コードの実行順序とは関係なく、入れ子の方式で解析される.
  • ダイナミックスコープは、プログラム実行時とコードの実行順序で決定される.動的スタックで動的に管理する.
  • var x = 10;
    function getX() {
        alert(x);
    }
    function foo() {
        var x = 20;
        getX();
    }
    foo();
  • は、静的な作用領域において、大域的な作用領域にxgetXfooの3つの変数があり、getXfooは、それぞれ自分の作用領域がある.foo関数を実行する場合、getX()は実行されるが、getXの定義位置はグローバルスコープであり、取り外されるxは10であり、20ではない.
  • は、動的作用領域において、このコードを実行する際に、x=10getXfooを順にスタックに押し込み、foo関数を実行し、関数にx=20をスタックに押し込み、getX()を実行する.このとき、スタックの一番近いx値は20であるので、alertの値も20である.
  • JavaScriptが使用する変数のスコープは静的な作用領域である.JSでのスコープは簡単に二つの部分に分けられます.グローバルスコープと関数スコープです.ES 5では、語法を用いて静的作用領域を環境管理する.
    語法環境には二つの部分が含まれています.
  • 環境記録
  • モダリティ
  • 関数宣言
  • 変数
  • その他…
  • 外部語法環境への参照
    環境レコード初期化
    JSコードが実行される前に、環境記録を初期化します.これから関数のイメージ、関数宣言、変数は先に関数の環境記録に入れます.
  • 形参会は初期化の際に値を定義しますが、関数内部で定義されている変数は定義されていない(不割当値)のみを宣言しています.これはJSのHoisting機構によって解釈される必要があります.具体的にはこの記事を見ることができます.
  • 以下のコードを例にとって、解析環境記録の初期化とコード実行のプロセス:
    var x = 10;
    function foo(y) {
        var z  = 30;
        function bar(q) {
            return x + y + z + q;
        }
        return bar;
    }
    var bar = foo(20);
    bar(40);
  • Step 1:グローバル環境を初期化する
  • グローバル環境
    環境記録(レコード)
    foo:
    x:undefined(変数を定義する代わりに宣言)
    bar:undefined(変数を定義する代わりに宣言)
    外部環境
    null
  • Step 2:実行x=10
  • グローバル環境
    環境記録(レコード)
    foo:
    x:10()
    bar:undefined(変数を定義する代わりに宣言)
    外部環境
    null
  • Step 3:var bar=foo(20)文を実行する前に、foo関数の環境記録を初期化する
  • foo環境
    環境記録(レコード)
    y:20(定義モザイク)
    bar:
    z:undefined(変数を定義する代わりに宣言)
    外部環境
    グローバル環境
  • step 4:var bar=foo(20)ステートメントを実行し、変数barはfoo関数から戻るbar関数
  • を受信する.
    foo環境
    環境記録(レコード)
    y:20
    bar:
    z:30(定義z)
    外部環境
    グローバル環境
  • step 5:bar関数を実行する前に、barのワード環境を初期化する
  • bar環境
    環境記録(レコード)
    q:40(定義形参q)
    外部環境
    foo環境
  • Step 6:foo関数内でbar関数
  • を実行します.
        x + y + z + q = 10 + 20 + 30 + 40 = 100 
    実はそんなに多いと言って、強調したいのです.形参の値は環境初期化の時に賦課されます.したがって、外形変数の値を保存する役割の一つです.
    本の閉鎖的な面接試験問題
    クローズドの面接問題について調べてみましたが、クローズドの応用シーンを具体的な例で説明します.最も一般的な答えは「JavaScript高級プログラム設計(第3版)」p 181から来ています.
    例:
    function creacteFunctions() {
        var result = new Array();
        for (var i = 0; i < 10; i++) {
            result[i] = function () {
                return i;
            }
        }
        return result;
    }
    この関数は長さ10の関数配列を返します.関数配列の3番目の関数を呼び出すと、コンソールにcreacteFunctions()[2]()を入力します.すなわち、実行関数配列の3番目の関数です.creacteFunctions()は関数配列に戻ります.[2]は3番目の関数の参照を取り、最後の()は3番目の関数を実行しますが、戻りの結果は期待される2ではありません.10.
    したがって、閉ループの挙動を予想通りにするためには、匿名関数を作成する必要がある.
    function creacteFunctions() {
        var result = new Array();
        for (var i = 0; i < 10; i++) {
            result[i] = function (num) {
                return function() {
                    return num;
                }
            }(i);
        }
        return result;
    }
    このとき、操作卓にcreacteFunctions()[2]()、すなわち実行関数配列の中の第3の関数を入力し、戻りは期待される2である.語法環境の初期化過程があって、ここもとても分かりやすくなりました.匿名関数の変形numは、毎回実行されるiの値を保持する.function(num){...}(i)のこの構造では、iは、この匿名関数を、成形numの実際の値として実行するので、各サイクルのnumは、直接iの値に初期化される.この部分構造をより明確に抽出するために、匿名関数をhelperと命名する.
    var helper = function (num) {
        return function() {
            return num;
        }
    }
    helper関数を使って第二段コードを書き換えます.
    var helper = function (num) {
        return function() {
            return num;
        }
    }
    function creacteFunctions() {
        var result = new Array();
        for (var i = 0; i < 10; i++) {
            result[i] = helper(i);
        }
        return result;
    }
    コンソールにcreacteFunctions()[2]()を入力し、出力も期待される2である.
    まだ続きますよ.閉め切ってもいいことが多すぎます.
    一言でまとめる
    作用域を本当に理解したら、閉包も理解できます.