Javascriptの語法のスコープ、呼び出しの対象と閉鎖


参照
スコープScope
Javascriptの関数は語法の作用領域に属しています.つまり、関数は実行される時の作用領域ではなく、定義された時の作用領域で実行されます.これはサイの本に書いてあります.しかし、「定義時」と「実行(呼び出し)時」の2つのものは、はっきりしない人がいます.簡単に言えば、一つの関数Aは、「定義時」はfunction A(){}という語句が実行されるときは、この関数を定義するときであり、Aが呼び出されるときは、A()という語句が実行されるときである.この二つの概念ははっきり区別しなければならない.
その语法作用域(以下「作用域」といいますが、特别に指定しない限り)は何ですか?それは抽象的な概念です.はっきり言って、それは「範囲」です.scopeは英語で範囲という意味です.一つの関数のスコープは、定義された時の「範囲」、つまりその外側の層の「範囲」であり、この「範囲」は外層の変数属性を含み、この「範囲」はこの関数の内部状態に設定されています.大域関数が定義されると、大域(この関数の外層)の「範囲」がこの大域関数の内部状態に設定されます.ネスト関数が定義されると、ネスト関数(外殻関数)の「範囲」がこのネスト関数の一つの内部状態に設定されます.この「内部状態」は、実際には作用ドメインチェーンとして理解され、以下のとおりである.
関数が呼び出されると、この関数では、その内部状態にアクセスできます.作用するフィールドチェーン全体の変数にアクセスできます.もちろん、外部変数も含まれます.(実際には「対象チェーンの呼び出し」から来ました.問題があるようです.続けて読んでください.)
以上のように、一つの関数の作用域は定義された時における「範囲」であり、Javascriptの関数作用域は関数が定義された時に確定されるので、静的な作用域であり、また静的作用域とも呼ばれる.
コール対象Call Object
一つの関数の呼び出しオブジェクトはダイナミックで、この関数が呼び出された時にのみ実装されます.関数が定義されているときにその作用分域鎖が決定されることを知っています.Javascriptインタプリタが関数を呼び出すと、このスコープの前に新しいオブジェクトが追加されます.この呼び出し対象の属性の一つは、アーグメンツオブジェクトが関数の実際のパラメータであるというアラグメンントという属性に初期化される.すべてのvar文で宣言されているローカル変数も、この呼び出し対象に定義されています.このとき、呼び出し対象はスコープのヘッダにあり、ローカル変数、関数形式パラメータ、アーグメンツオブジェクトはすべてこの関数の範囲にあります.もちろん、このときはローカル変数と関数形式パラメータとAgmentsオブジェクトが、作用ドメインチェーン内の同名の属性をカバーします.
スコープ、スコープ、呼び出しオブジェクト間の関係
私の理解では、スコープは抽象的で、オブジェクトを呼び出すのは実用的です.
関数が定義されているとき、実際にはその外層関数が実行されるとき、その決定された作用域チェーンは実際にその外層関数の呼び出し対象チェーンである.関数が呼び出されると、そのスコープは定義に従って決定されたスコープチェーン(外部関数の呼び出し対象チェーン)に実用化された呼び出しオブジェクトを付加する.したがって、関数のスコープは実際にオブジェクトチェーンを呼び出します.関数が呼び出されたとき、そのスコープチェーン(または呼び出し対象チェーン)は実際に定義されたときに決定されるスコープチェーンの一つのスーパーセットである.
それらの間の関係は、作用領域⊃__がドメインチェーン⊇を使ってオブジェクトを呼び出すと表してもよい.
回りくどいので、例を挙げて説明しましょう.

function f(x) {
    var g = function () { return x; }
    return g;
}
var g1 = f(1);
alert(g1());  //   1
大局を以下のような大きな匿名関数と見なしたとする.

(function() {
    //       
})();
その例は次のように見える.

(function() {
    function f(x) {
        var g = function () { return x; }
        return g;
    }
    var g1 = f(1);
    alert(g1());  //   1
})();
   0.大域の大匿名関数が定義されている場合、外層がないので、その作用ドメインチェーンは空です.
   1.グローバルの大匿名関数が直接実行され、グローバルのスコープには「グローバル呼び出しオブジェクト」が一つしかない.
   2.関数fが定義されており、このとき関数fの作用ドメインチェーンは、その外層の作用ドメインチェーンであり、すなわち「グローバル呼び出しオブジェクト」である.
   3.関数f(1)が実行され、その作用ドメインチェーンは新しいf(1)呼び出しオブジェクトに関数fが定義されるときの作用ドメインチェーン、すなわち'f(1)呼び出しオブジェクト->グローバル呼び出しオブジェクト'である.
   4.関数g(g 1に戻るなら、g 1と名付けましょう)f(1)で定義されていますが、その外層の関数f(1)の作用域チェーン、すなわち「f(1)呼び出しオブジェクト-」グローバル呼び出しオブジェクト'です.
   5.関数f(1)は、関数gの定義をg 1に返します.
   6.関数g 1が実行され、その作用ドメインチェーンは、新しいg(1)呼び出しオブジェクトに外層f(1)を付加した作用ドメインチェーンであり、すなわち'g 1呼び出しオブジェクト->f(1)呼び出しオブジェクト->グローバル呼び出しオブジェクト'である.
このように見ればよく分かりますよね.
クローズドClouer
クローズドの一つの簡単な言い方は、ネスト関数がネスト関数の外注によって使用されると、クローズドが形成されるということです.
前の例は実は一つのクローズドです.g 1はf(1)内部で定義されていますが、f(1)が戻ってから実行されます.閉ループの効果の一つは、ネスト関数fによって返された後、その内部のリソースが解放されないことを示しています.g関数を外部から呼び出した場合、gはfの内部変数にアクセスできます.この特性によって、多くの優雅なコードが書けます.
例えば、一つのページに統一されたカウンタを作成します.

var counter  = (function() {
    var i = 0;
    var fns = {"get": function() {return i;},
               "inc": function() {return ++i;}};
    return fns;
})();
//do something
counter.inc();
//do something else
counter.inc();
var c_value = counter.get();  //now c_value is 2
このようにメモリには変数iが維持されています.プログラム全体の他の場所では、iの値を直接操作することはできません.
setTimeout(fn,delay)の場合は、fnという関数のハンドルにパラメータを伝えることはできませんが、必要なパラメータをクローズドする方法でfn内部にバインドすることができます.

for(var i=0,delay=1000; i< 5; i++, delay +=1000) {
    setTimeout(function() {
        console.log('i:' + i + " delay:" + delay);
    }, delay);
}
このようにプリントした値は全部

delay:6000
i:5 delay:6000
i:5 delay:6000
i:5 delay:6000
i:5 delay:6000
クローズドにすると、伝えたいパラメータをバインドしやすくなります.

for(var i=0, delay=1000; i < 5; i++, delay += 1000) {
    (function(a, _delay) { 
        setTimeout(function() { 
            console.log('i:'+a+" delay:"+_delay);
        }, _delay);
    })(i, delay);
}
出力:

i:0 delay:1000
i:1 delay:2000
i:2 delay:3000
i:3 delay:4000
i:4 delay:5000
クローズドはもう一つよく使われているところがあります.イベントを結びつけるコールバック関数の時です.同様の道理で、結合された関数のハンドルはパラメータを作ることができませんが、閉じた形でパラメータを結合することができます.
締め括りをつける
   1.関数の品詞作用ドメインと作用ドメインチェーンは異なるものであり、語法作用ドメインは抽象概念であり、作用ドメインチェーンは実用化されたコール対象チェーンである.
   2.関数は定義された時に、その外層の関数として実行されます.
   3.関数は定義された時にその語法のスコープは確定されましたが、依然として抽象的な概念です.なくても実用化されません.
   4.関数は定義された時に、外層関数の作用ドメインチェーンというものを決定しました.これは実用化されたものです.
   5.関数は何度も呼び出された時、その作用ドメインチェーンは全部違っています.
   6.クローズドが強い.サイの本は正しいです.これらを理解すれば、高級Javascriptプログラマーと自称することができます.これらの概念をうまく利用することで、Javascriptの多くのデザインパターンを楽しむことができます.
-EOF-