JavaScriptのクロージャ
0 91

概説

クロージャとは、「関数の外側で定義された変数を持つ関数の実行時オブジェクト」です。

 JavaScriptでは、関数もオブジェクトであり、生成から廃棄されるまでのライフルサイクルを持ちます。関数が外側の関数で定義されたローカル変数を参照していれば、関数オブジェクト生成時の実行環境も関数オブジェクトの属性の一部(クロージャスコープ属性)として維持し続けられます。

サンプル

下記のコードを例として説明します 。

var x = 1; 
function Counter() { 
  var n = 0; 
  return function () { 
     n = n + x;     
     return n;   
  }; 
} 

関数Counter()は、内部でローカル変数nを定義して、それを参照するクロージャを返します。

実行1  

var a = Counter(); 
console.log("a()=" + a()); 
console.log("a()=" + a()); 
console.log("a()=" + a()); 
console.log("a()=" + a()); 

上記コードの実行結果は次の通りになります。 

a()=1 
a()=2 
a()=3 
a()=4 

変数nの値が保持されていることは分かります。

Counter()実行後の環境の状態

┌────────┐     
     グローバル環境       ┌───────┐       
       x => 1         ←─  Counterの環境       
       a => function           n => 0      ←─ クロージャa   
└────────┘               └───────┘ 

実行2  

var a = Counter(); 
var b = Counter(); 
console.log("a()=" + a()); 
console.log("a()=" + a()); 
console.log("b()=" + b()); 
console.log("a()=" + a()); 
console.log("b()=" + b()); 

実行結果は次の通りです 。

a()=1 
a()=2 
b()=1 
a()=3 
b()=2 

関数Counter()が実行されるごとに異なる環境が生成されることはわかります。  

Counter()2回実行後の環境の状態 

┌────────┐                ┌──────────┐    
   グローバル環境    ←─  1回目のCounterの環境       
     x => 1                      n => 0          ←─ クロージャa       
     a => function        └──────────┘       
     b => function        ┌──────────┐   
└────────┘          ←─ 2回目のCounterの環境  
                                 n => 0          ←─ クロージャb 
                          └──────────┘ 
0 91
みんなのツイート (0)

関連サマリー


  • JavaScript 0 Votes 73 閲覧数


    Javascriptは、ほかの言語と比べると、以下のような特徴を持っております。

    完全なオブジェクトベース

    JavaScriptでは、数値や文字列、配列、関数などのデータはすべてオブジェクトです。例えばStringオブジェクトは文字列の長さを返すlengthというプロパティを持っていますので、“Hi,world!”.lengthを参照すると、 9がリターン値になります。 
    JavaScriptのオブジェクトは、単純に言うと「キーと値のペアの集合」です。ですので、これは一般的なプログラミング言語でよくあった連想配列の性質を持っていると言えます。

    プロトタイプを使ったオブジェクト指向

    Java や C++ といったクラスベースのオブジェクト指向言語はクラスとインスタンスという、2つの異なる実体があるという概念に基づいています。 
    クラスはあるオブジェクトの集合を特徴付けるすべてのプロパティ(Java ではメソッドとフィールドを、C++ ではメンバをプロパティと見なす)を定義します。 
    クラスとはそれが表すオブジェクトの集合という抽象的なものです、例えば、Employeeクラスは従業員すべてを含む集合を現します。 
    一方、インスタンスはクラスを実例にしたものです、例えば、Victoriaさんは、Employeeクラスのインスタンスとなることができます。

    JavaScriptのオブジェクト指向は、プロトタイプを利用しています。クラス/インスタンスの区別がなく、単にオブジェクトがあるだけです。但し、原型的なオブジェクトという概念がありまして、このオブジェクトは新しいオブジェクトの初期プロパティを取得する元になるテンプレートとして使用されます。 
    どのオブジェクトも、作成時にも実行時にもそれ独自のプロパティを指定できます。さらに、どのオブジェクトも別のオブジェクトに対するプロトタイプとして関連付けることができます。

    関数型プログラミングの一部サポート

    JavaScriptでは関数型言語の一部の機能が備わっています。関数型言語それ自体は難しいかも知れませんが、JavaScriptで関数型を考えるにあたっては、下記三点を抑えられればよいです。

    すべての関数が値を返す関数に副作用がない(参照透過)関数を値として扱える すべての関数が値を返す

    すべての関数は値を返します、明示的な実装がなければ、返却値はundefinedになります。

    関数に副作用がない

    簡単に考えると、下記2点となります。

    関数は外側の変数を変更しない参照渡しの引数の値を変更しない 関数が値として扱える

    関数が値として扱えるので引数に関数を渡して処理を委譲、もしくは、関数を返り値として処理を委譲する事もできます。 
    これらの関数を高階関数と言います。 
    さらに、関数は、すべてクロージャであり、定義された時の環境への参照をもっているため、クロージャの性質を利用して、JavaSript固有のざまざまなカプセル仕組みを実現することができます。

    JavaScriptはよく、「少ないソースコードで高機能のロジックを簡潔に実現できる」プログラミング言語として高く評価されていますが、これは、ほとんどこの関数のパワーからできているものと考えれます。


  • JavaScript 0 Votes 68 閲覧数


    JavaScriptは 最初は Sun Microsystems社とNetscape Communications社が開発した、 Webブラウザなどでの利用に適したスクリプト言語です。ブラウザ以外のソフトウェアにも簡易な制御プログラムの記述用言語として移植されており、 Microsoft社のWindowsやMacromedia社の「Flash」などがあります。

    各社の実装に微妙な違いがあり、ブラウザによって使えない機能があったり同じプログラムでも挙動が異なったりする問題があったため、ヨーロッパの標準化団体ECMAがソフトウェアメーカーに呼びかけて、通称「ECMAScript」と呼ばれる標準を発行しました。2014年5月現在ECMAScriptの最新バージョンは2009年12月に策定されたECMAScript 5th Editionで6th editionの策定も進みつつあっています。

    JavaScriptとECMAScript

    以下の表でJavaScriptとECMAScriptのバージョン対応を示します。

    JavaScriptバージョン公開日ECMAScriptとの対応1.01996/03-1.11996/08-1.21997/07-1.31998/10edition 1 edition 21.4廃棄1.52000/11edition 31.62005/111.72006/101.82008/061.8.52010/07edition 51.8.6--2.0-edition 6 主要ソフトウェアのJavaScript対応

    以下の表で主要ソフトウェアのバージョン毎のJavaScript対応を示します。

    ソフトウェアバージョン呼称対応するJavaScriptバージョンChrome33JavaScript1.7Firefox1.0JavaScript1.51.51.62.01.73.01.841.8.5171.8.6IE6JScript5.61.57JScript5.78JScript5.89JScript?1.8.5Adobe Acrobat11JavaScript1.5?

  • JavaScript 0 Votes 62 閲覧数


    構文構造を利用

    例1:

    var o = { a: 1 };

    上記のoは Object.prototype をプロトタイプとして生成され、 o.hasOwnProperty('a') がtrueを戻します。

    例2:

    var a = ["yo", "whadup", "?"];

    上記のaは Array.prototype をプロトタイプとして生成され、三つの要素を持ちます。

    コンストラクタ関数を利用

    JavaScriptに於ける「コンストラクタ」は、関数を new 演算子を使って呼び出す事で実現可能です。 

    下記のコードを例とします。

    function Graph() { this.vertexes = []; this.edges = []; } Graph.prototype = { addVertex: function(v){ this.vertexes.push(v); } }; var g = new Graph();

    上記のコードで生成されるg は「vertexes」と「edges」の自身のプロパティを持つオブジェクトであり、 g.[[Prototype]] はインスタンス化する時点の Graph.prototype の値になります。

    Object.Create メソッドを利用

    ECMAScript 5 は Object.create という新しいメソッドを紹介しています。このメソッドを呼び出すと、新しいオブジェクトが生成されます。
    関数の最初の引数が、このオブジェクトのプロトタイプになります。 

    var o1 = { // o1 ---> Object.prototype ---> null a: 1 }; var o2 = Object.create(o1); // o2 ---> o1 ---> Object.prototype ---> null console.log(o2.a); // 1 (継承された) var o3 = Object.create(o2); // o3 ---> o2 ---> o1 ---> Object.prototype ---> null var o4 = Object.create(null); // o4 ---> null console.log(o4.hasOwnProperty); // undefined、なぜなら o4 は Object.prototype から継承していないからです。

  • JavaScript 0 Votes 68 閲覧数


    プロトタイプベースとクラスベース

    C++、Javaのようにクラス-インスタンスという概念を使用するオブジェクト指向言語のことを、クラスベースオブジェクト指向言語(Class Based Object Oriented Language)と言います。
    それとは異なり、JavaScriptでは、クラス-インスタンスという考え方をせず、オブジェクトは別なオブジェクトを元(プロトタイプ)にして独自の特徴を付加することで存在する、という考え方をします。
    このようなオブジェクト指向言語のことを、プロトタイプベースオブジェクト指向言語(Prototype Based Object Oriented Language)と言います。

    JavaScriptでは、オブジェクト自身が独自の特徴を付加するので、オブジェクトにプロパティを追加したり削除したりすることができます。

    コンストラクタという機構

    JavaScriptでオブジェクトを生成するには、コンストラクタとnewという演算子を使用します。 
    実はコンストラクタとは関数オブジェクトの事です、全ての関数はオブジェクトを生成するコンストラクタになる可能性がありますが、まずは一番シンプルな例を見てください。

    単純なコンストラクタ 

    function SimpleConstructor() {} var obj = new SimpleConstructor();

    普通にfunction文で定義した関数にnewをつけて呼び出しているだけです。こうすることで、新しいオブジェクトが生成され、戻り値として返されます。 
    return文を記述しなくても生成されるオブジェクトが返される事に注意してください。

    通常、コンストラクタとして呼ばれる関数の中では、生成しようとするオブジェクトの初期化を行います。 

    function PropSetConstructor() { this.prop1 = 10; this.prop2 = 20; } var obj1 = new PropSetConstructor(); alert(obj1.prop1); // 10 と表示される。 var obj2 = new PropSetConstructor(); alert(obj2.prop1); // 10 と表示される。

    コンストラクタとして関数オブジェクトを実行した場合、thisには今新たに生成されようとしているオブジェクトが入ります。
    従って、thisにプロパティを設定してやれば、newして作成されるオブジェクトの初期定義を行う事ができます。
    こうする事で、同じコンストラクタを用いて複数のオブジェクトを生成すると、同じプロパティを持つオブジェクトを簡単に作成する事ができます。

    もちろんdelete演算子を使用すればプロパティを削除できるので、同じコンストラクタから作成されても常に必ず同じプロパティを持つとは限らない事に注意してください。 

    暗黙の参照

    プロトタイプベースのオブジェクト指向言語では、オブジェクトトは別のオブジェクトをプロトタイプとしてできていると考えます。JavaScriptではこれを暗黙の参照という形で実現している。オブジェクトAをプロトタイプとしているオブジェクトBは、オブジェクトAに対し暗黙の参照を持っているという状態になります。 
    Javaで言うなれば、非staticなinner classのインスタンスがenclosing classのインスタンスに対し、暗黙の参照を持っている状態を考えてもらえば良いです。つまりしたのような状態になります。 
    暗黙の参照: 

    var objectA = ... // objectB は objectA に対し暗黙の参照を持っている // (objectAはobjectBのプロトタイプである)。 var objectB = ... objectA.hoge = 10; // objectA の hoge プロパティをここで設定する。 alert(objectB.hoge); // 10 と評価される。

    すなわち、あるオブジェクトがプロパティを評価された時、自分自身がそのプロパティを持っていなければ、暗黙の参照をたどって、その先のオブジェクトのプロパティを評価します。 
    これがJavaScriptにおけるプロトタイプの仕組みです。

    prototypeオブジェクト

    全ての関数オブジェクトはprototypeというプロパティを保持している。関数オブジェクトを定義した直後では、このprototypeには何もプロパティを持たないシンプルなオブジェクトを参照しているが、別のオブジェクトを代入したり、新たなプロプロパティを設定したりする事が可能です。 
    その関数オブジェクトをコンストラクタとして生成されたオブジェクトは、コンストラクタのprototypeプロパティに代入されているオブジェクトに対し、暗黙の参照を持つのです。つまりprototypeが指すオブジェクトトがプロトタイプとなるというわけです。  

    具体的にコードは以下: 
    prototypeオブジェクト 

    function PrototypeTestConstructor() {} // prototype オブジェクトに prop1 というプロパティを設定。 PrototypeTestConstructor.prototype.prop1 = 30; var obj = new PrototypeTestConstructor(); alert(obj.prop1); // 30 と表示される。

    つまりPrototypeTestConstructorによって生成されたオブジェクトは、PrototypeTestConstructor.prototypeが指すオブジェクトを暗黙的に参照するようになります。 
    あるコンストラクタをnewしてできるオブジェクトが全てそのprototypeへの暗黙の参照を持つので、そのプロパティはクラス変数の様にnewされたオブジェクトで共有されることとなる。次の例を見てください。  

    代入はprototypeをたどらない。 

    function Constructor() {} Constructor.prototype.prop1 = 30; var objA = new Constructor(); var objB = new Constructor(); // [α] alert(objA.prop1) // 30 と表示される。 alert(objB.prop1) // 30 と表示される。 objA.prop1 = 100; // [β] alert(objA.prop1) // 100 と表示される。 alert(objB.prop1) // 30と表示される

    というのも、読み取り評価の時は、暗黙の参照をたどるのですが、代入やdelete演算子は、たどらないのである。従って、objA.prop1 = 100;を行った時点で、objAそのものにprop1というプロパティが新たに作られ、そこに100が代入されるのです。よってConstructor.prototype.prop1の値は変わらないままとなります。

    この場合、objA.prop1を呼び出すと、objA自身にはprop1があるため、その値である100と評価され、objB.prop1を呼び出すと、暗黙の参照をたどり30と評価されます。

    この仕組みがプロトタイプベース言語であるJavaScriptのオブジェクトの根幹となります。

    プロトタイプチェーン

    コンストラクタのprototypeには、オブジェクトも代入できるので、prototypeに代入したオブジェクトが別のオブジェクトをプロトタイプにしている事もあります。そもそも全てのオブジェクトはObject.prototypeを暗黙的に参照しています。

    こうしたプロトタイプの連鎖のことをプロトタイプチェーンと呼びます。 
    プロトタイプチェーン: 

    var objA = new Object(); objA.prop1 = 10; function Func1() {} Func1.prototype = objA; var objB = new Func1(); function Func2() {} Func2.prototype = objB; var objC = new Func2(); alert(objC.prop1); // 10 と評価される。

     以上までがJavaScriptのプロトタイプのメカニズムです。JavaやC++といったクラスベースの言語には根本的に無い考え方なので若干戸惑うかも知れないが、全てのオブジェクトがが実体を持った別のオブジェクトトに連鎖しているということが分かれば整理できます。

    ではあるオブジェクトのプロパティを評価した際、仮に何らかの値が返ってきたとしても、そのオブジェクト自身がプロパティを持っているのか、そのオブジェクトのプロトタイプが持っているのか、判断することができません。     そこで、JavaScriptでは全てのオブジェクトに(すなわちObject.prototypeに)、hasOwnPropertyというメソッドが定義されている。これは引数で与えた文字列に一致するプロパティを、そのオブジェクト自身が持っているかどうかを判断してくれるメソッドです。  具体例を見てください。 

    function Constructor() {} Constructor.prototype.prop1 = 30; var objA = new Constructor(); // objA 自体は持っていないので false。 alert(objA.hasOwnProperty("prop1")); objA.prop1 = 100; // objA 自体が持っているので true。 alert(objA.hasOwnProperty("prop1"))

    このhasOwnPropertyメソッドを使うことで、そのオブジェクトト自身にプロパティがあるかどうかを判断することができます。

    継承

    継承に関して言えば、 JavaScript には 1 つだけオブジェクトに関する概念があります。あるオブジェクトはプロトタイプと呼ばれる、他のオブジェクト(または null )への内部的な繋がりを持ちます。このオブジェクトは、あるオブジェクトがそのプロトタイプとして null を持つまで、なおプロトタイプを持ちます。このような、オブジェクトが他のオブジェクトのプロトタイプとなることの連鎖を、プロトタイプチェーンと呼びます。

    プロパティの継承

    JavaScript のオブジェクトはプロパティ(自身のプロパティを指す)の動的な「かばん」で、プロトタイプオブジェクト(または null )への繋がりを持っています。  あるオブジェクトにプロパティをセットすると、自身のプロパティが作られます。この取得と設定の動作の規則の唯一の例外は、 getter または setter とのプロパティの継承が起こるときです。

    「メソッド」の継承

    JavaScript それ自体に「メソッド」はありません。 JavaScript には関数があり、これらの関数はプロパティの値として使用することができます。 関数の継承(メソッドのオーバーライドの一種)は、上で見せたような property shadowing を含めたどのような値とも、同じように働きます。

    関数がオブジェクトのプロプロパティ(自身の、または継承された)であることのたった1つの違いは、関数が実行されるときの this の値です。