おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

関数のスコープとクロージャについてまとめ

こないだ関数を勉強したときに飛ばしたクロージャについて調べたのでメモ。

覚えるのがめんどいのであとまわし。

javascriptの基礎その2(関数について) - おもしろWEBサービス開発日記

クロージャの定義

wikipediaにはこんな風に書いてあった。

クロージャ (Closure) は、プログラミング言語において引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決する関数のことである。

クロージャ - Wikipedia

サイ本にはこんな風に書いてあった。

JavaScriptの関数は、実行するコードと、コードを実行するスコープを組み合わせたものです。コードとスコープが対になったものを、コンピュータサイエンスの分野ではクロージャと呼んでいます。

そもそもスコープまわりがよくわかってないことに気づいた。

スコープとは

変数が特定の名前で参照される範囲。JavaScriptでは、スコープチェーンを使ってスコープを定義している。

スコープチェーンとは

スコープチェーンとは、オブジェクトを並べたもの。JavaScriptは、変数を探すときにスコープチェーンの先頭のオブジェクトのプロパティから探していく。関数外で定義された変数はグローバルオブジェクトから探索され、関数内で定義された変数は、まず関数のCallオブジェクトから探索されて、なければグローバルオブジェクトを探索する。


スコープチェーンはどうやって定義されるんだろう?

スコープチェーンが定義される順序

JavaScriptインタプリタは、関数が呼び出されると、関数のスコープにまず関数定義時のスコープチェーンを設定する。例えば下記の場合、hoge()関数が呼ばれたときに、まずfoo()関数のスコープチェーンを呼び出して設定するってこと。

function foo(){
  function hoge(){
  //...
  }
  //...
}

次に、Callオブジェクトと呼ばれるオブジェクトを生成する。生成の際に、

  • argumentsプロパティの生成
  • 名前付きの引数をCallオブジェクトに定義
  • var文で宣言されたローカル変数をCallオブジェクトに定義

などが行われる。その後、Callオブジェクトをスコープチェーンの先頭に追加する。

静的なスコープとは

「実行時のスコープではなく、定義時のスコープを使うこと」を静的スコープというみたい。

つまり、下記だったらfoo()のスコープはfunction foo()のスコープだよ!funtion hoge()じゃないよ!ってことかな。すごく当たり前な気がする。

function foo(){
  // ...
}
function hoge(){
  foo();
}

そしてJavaScriptの関数は、全て静的なスコープ。

JavaScriptで使われる「クロージャ

ここまでの内容だと、JavaScriptの関数は全てクロージャな気がするけど、どうも一般的なクロージャJavaScriptで使われる「クロージャ」は少し意味が違うみたい。JavaScriptでは、入れ子型の関数が親の関数以外で使われたときに「クロージャを使った」と呼ばれるよう。


じゃあどんなときにクロージャを使うといいんだろう?

JavaScriptクロージャ使用例その1

下記のコードだと、外側からuniqueID.idを弄ることができてしまい嬉しくない。

uniqueID = function() {
  if (!arguments.calee.id) arguments.calee.id = 0;
  return arguments.calee.id++;
}

なのでこんな風に「クロージャを使う」とidが隠蔽されて嬉しい。

uniqueID = (function() {
  var id = 0;
  return function() { return id++; }
}();

JavaScriptクロージャ使用例その2

その1はいまいちわかりにくいのでもう一つ例を書きます。下記のmakeProperty()関数はクロージャを使って、valueを隠蔽しています。

function makeProperty()(o, name, predicate) {
  var value;
  o["get" + name] = function() { return value; };
  o["set" + name] = function(v) {
    if (predicate && !predicate(v))
      throw "set" + name + ": invalid value" + v;
    else
      value = v;
  };
}
var o = {};

makeProperty(o, "Name" function(x) { return typeof x == "string"; });
o.setName("Frank");
print(o.getName());

終わりに

まだ少し曖昧な部分があります。それ違うよって部分があったら突っ込みください。