jsで面白いクローズド(closure)

6356 ワード

一、変数のスコープ
クローズドを理解するには、まずJavascriptの特殊な変数のスコープを理解しなければならない.
変数のスコープは、大域変数と局所変数の2つにすぎません.
Javascript言語の特殊な点は、関数内部で直接大域変数を読み取ることができることです.
var n=999;
function f1(){
    alert(n);
}
f1(); // 999
一方、関数の外部では当然、関数内の局所変数を読み取ることができません.
function f1(){
  var n=999;
}
alert(n); // error
関数内部で変数を宣言する場合は、必ずvarコマンドを使用してください.使わないなら、実際にグローバル変数を宣言しました.
function f1(){
  n=999;
}
f1();
alert(n); // 999
 
二、外部からローカル変数を読み取るにはどうすればいいですか?
様々な理由から関数内の局所変数を得る必要があります.しかし、前にも話しましたが、普通の状況ではできません.変通方法でしか実現できません.
それは関数の内部で、もう一つの関数を定義することです.
function f1(){
  var n=999;
  function f2(){
    alert(n); // 999
  }
}
上のコードでは、関数f 2が関数f 1の内部に含まれており、f 1の内部のすべての局所変数は、f 2に対して可視である.しかし、逆にだめです.f 2内部の局部変数は、f 1に対しては見えません.これはJavascript言語特有の「連鎖作用領域」構造であり、サブオブジェクトは一段ずつ上に向かってすべての親オブジェクトの変数を探します.したがって、親オブジェクトのすべての変数は、子オブジェクトに対しては見えますが、逆は成立しません.
f 2は、f 1の局所変数を読み取ることができるので、f 2を戻り値とすると、f 1の外部で内部変数を読み出すことができるのではないでしょうか?
function f1(){
  var n=999;
  function f2(){
    alert(n);
  }
  return f2;
}
var result=f1();
result(); // 999
三、クローズドの概念
前のセクションのコードのf 2関数は、閉じられています.
各種の専門文献の「クローズド」の定義はとても抽象的で、分かりにくいです.私の理解では、クローズドとは他の関数の内部変数を読み取ることができる関数です.
Javascript言語では、関数内部のサブ関数だけが局所変数を読み取ることができるので、クローズドを単に「関数の内部に定義された関数」と理解することができます.
したがって、本質的には、クローズドとは、関数の内部と関数の外部をつなぐ橋のことである.
 
四、クローズドの用途
クローズドは多くのところで使えます.その最大の用途は二つあります.一つは前に述べた関数内部の変数を読み取ることができます.もう一つはこれらの変数の値を常にメモリに保存させます.
この言葉はどうやって理解しますか?下のコードを見てください.
function f1(){
  var n=999;
  nAdd=function(){n+=1}
  function f2(){
    alert(n);
  }
  return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
このコードの中で、resultは実はクローズドf 2関数です.全部で二回運行しました.一回目の値は999、二回目の値は1000です.これは、関数f 1の局所変数nがメモリに保存されており、f 1呼び出し後に自動的に消去されないことを証明している.
どうしてですか?f 1はf 2の親関数であり、f 2はグローバル変数を与えられ、f 2は常にメモリにあり、f 2の存在はf 1に依存しているため、f 1も常にメモリにあり、呼び出しが終了した後、ゴミ回収メカニズムによって回収されることはない.
このコードのもう一つの注目すべき点は、「nAdd=function(){n+=1]」という行であり、まずnAddの前にvarキーワードが使用されていないため、nAddは局所変数ではなくグローバル変数である.第二に、nAddの値は匿名関数であり、この匿名関数自体もクローズドであるため、nAddは相当であり、関数の外部で関数内部の局所変数を操作することができる.
 
五、クローズドを使う注意点
1)クローズドは関数の変数がメモリに保存されるため、メモリの消耗が大きいので、クローズドを濫用してはいけません.そうでないと、ウェブページの性能問題が発生し、IEでメモリのリークを引き起こす可能性があります.解決方法は、関数を終了する前に使用しないローカル変数をすべて削除します.
2)親関数の外部で、親関数の内部変数の値を変更します.したがって、親関数を対象として使用する場合は、クローズドをそのパブリックメソッドとして、内部変数をそのプライベート属性として扱い、親関数内部変数の値を勝手に変えないように注意してください.
 
六、思考問題
下記の2つのコードの運行結果を理解できれば、クローズドの運行メカニズムを理解してもいいはずです.
コードセグメント1
var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
      return function(){
        return this.name;
      };
  }
};
alert(object.getNameFunc()());
コードセグメント2
var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    var that = this;
    return function(){
      return that.name;
    };
  }
};
alert(object.getNameFunc()());