【JavaScript 中級講座】クロージャの学習
本講座では、クロージャを学習する。 サンプルプログラムを3つ掲載した後、 (少しだけ)実践的なプログラムを見る。
クロージャを理解するための準備段階
002//変数を宣言
003var count = 0 ;
004
005//関数を宣言
006function run () {
007 count ++ ;
008 alert ( "run" + count ) ;
009}
010//[run1]
011run ();
012
013//[run2]
014run ();
015</script>
プログラムを実行すると「run1」「run2」と表示する。
プログラムを解説する。 3行目で変数countを宣言して、0を代入する。 6行目でrun()関数を定義する。run()関数の処理は後ほど見る。 11行目でrun()関数を実行する。
003var count = 0 ;
004
005//関数を宣言
006function run () {
007 count ++ ;
008 alert ( "run" + count ) ;
009}
run()関数が実行すると、7行目で変数countの値を加算する。 この「変数count」は、3行目の変数countである。 変数countはrun()関数の外に存在する。 しかし、関数内から関数外の変数を参照できる。 今回、run()関数内から関数外の変数countを利用する。 8行目で、文字列「run」と変数countを文字列結合して表示する。 ダイアログに「run1」と表示する。
002//変数を宣言
003var count = 0 ;
004
005//関数を宣言
006function run () {
007 count ++ ;
008 alert ( "run" + count ) ;
009}
010//[run1]
011run ();
012
013//[run2]
014run ();
015</script>
14行目で、run()関数を再び実行する。 run()関数が実行すると、7行目で変数countの値が加算する。 変数countは3行目の変数countである。 いま、値「1」が入っている。 7行目を実行すると、変数countの値が加算され「1」から「2」になり、 8行目で文字列結合して表示する。 ダイアログに「run2」と表示する。 解説は以上である。
関数の中から、関数の外の変数を利用できる
このサンプルプログラムで、 関数内から関数外の変数を参照できる事を学習した。 もう一つサンプルプログラムを見る。
002//変数を宣言
003var count = 0 ;
004
005//関数を宣言
006function run () {
007 count ++ ;
008 alert ( "run" + count ) ;
009}
010//[run1]
011run ();
012
013//countの値を変更
014count = 10 ;
015
016//[run11]
017run ();
018</script>
run()関数は変更していない。11行目から解説する。 11行目でrun()関数を実行する。run()関数が実行すると、7行目で変数countの値が加算される。 変数countの値は「0」から「1」へ変更する。
さて、14行目で変数countに10を代入する。変数countは3行目のcountである。 変数countには「1」が入っているが、 14行目を実行すると「10」が代入される。 17行目でrun()関数を実行する。run()関数が実行すると、7行目で変数countの値を加算する。 変数countの値が「10」から「11」へ変更する。8行目で文字列結合して変数countの値を表示する。 ダイアログに「run11」と表示する。
変数countの値は関数の中から変更できるし、また(14行目のような)宣言した同じ場所からでも 変更可能である。
はじめてのクロージャ
002var click = function () {
003
004 //変数を宣言
005 var count = 0 ;
006
007 //関数を宣言
008 function run () {
009 count ++ ;
010 alert ( "run" + count ) ;
011 }
012 return run;
013}
014var a = click();
015
016//[run1]
017a ();
018
019//[run2]
020a ();
021</script>
プログラムを実行すると「run1」「run2」と表示する。
プログラムを解説する。2行目で関数を定義して、 変数clickへ代入する。関数の処理内容は複雑である。 後ほど解説する。。 14行目でclick()関数を実行して、 戻り値を変数aへ代入する。 では、click()関数の処理を見る。
003
004 //変数を宣言
005 var count = 0 ;
006
007 //関数を宣言
008 function run () {
009 count ++ ;
010 alert ( "run" + count ) ;
011 }
012 return run;
013}
まず、5行目で変数countを宣言して「0」を代入する。 8行目で、run()関数を定義する。 click()関数の中にrun()関数を定義する形である。 さて、ここでは関数を定義しているだけなので、 run()関数が実行する事は無い。12行目で、run()関数を戻り値に設定している。
関数を実行する場合は、関数名の後ろに括弧を記述する。 12行目のrun()関数は、関数名「run」の後ろに括弧が記述されていない。 つまり、ここでrun()関数が実行するわけではない。 戻り値としてrun()関数を指定しているだけである。 click()関数を実行すると、実行結果としてrun()関数が戻る。
003
004 //変数を宣言
005 var count = 0 ;
006
007 //関数を宣言
008 function run () {
009 count ++ ;
010 alert ( "run" + count ) ;
011 }
012 return run;
013}
014var a = click();
015
016//[run1]
017a ();
14行目でclick()関数を実行すると、戻り値としてrun()関数が戻る。 そのrun()関数を変数aで受け取る。 変数aはrun()関数を参照する。 run()関数を参照するだけであり、ここでrun()関数を実行するわけではない。 17行目でaに代入されたrun()関数を実行する。 変数名の後ろに括弧を記述すると、 変数に入っている関数を実行する。 ここで、run()関数が実行する。
008 function run () {
009 count ++ ;
010 alert ( "run" + count ) ;
011 }
変数aに入っているrun()関数である。この関数が実行する。 では、解説する。まず、9行目で変数countを・・・、 変数countを・・・。変数countが無い?
クロージャ 関数とその環境(関数内で利用する変数)がセットになったもの
クロージャを解説する。 こんかい、run()関数で変数countを利用するが、 そもそもrun()関数内に変数countが存在しない。 変数countとは何か?
変数countは、click()関数が実行した時に宣言した変数である。
003
004 //変数を宣言
005 var count = 0 ;
006
007 //関数を宣言
008 function run () {
009 count ++ ;
010 alert ( "run" + count ) ;
011 }
012 return run;
013}
click()関数を実行した時、5行目で変数countを宣言している。 click()関数が実行すると、変数countに「0」が代入される。 その後、変数countはclick()関数内で利用されることなく、 click()関数の処理は終了する。
click()関数の処理が終了した時点で、変数countは消えるはずである。 しかし他の関数、この場合はrun()関数に参照されているため、 run()関数と同じ環境で残る。 消えない。
簡単な話、「関数から参照される外部変数は関数と共に存在します」と言う事である。 つまりrun()関数は、変数countとセットである。
002 //変数を宣言
003 var count = 0 ;
004
005 //関数を宣言
006 function run () {
007 count ++ ;
008 alert ( "run" + count ) ;
009 }
010}
run()関数のイメージである。なにやら、オブジェクトにcountプロパティと run()関数が入っているように見える(あくまでもイメージ図である。実際のプログラムとは異なる)。
さて、run()関数が実行すると変数countが加算されて、 10行目で変数countの値を表示する。 ダイアログに「run1」と表示する。
サンプルプログラムでは、もう一度run()関数を実行する。 もう一度run()関数を実行すると、 9行目で変数countの値を加算する。変数countの値が「1」から「2」へ変更する。 10行目でダイアログに「run2」と表示する。解説は以上である。
つぎに、今のプログラムを別の方法で記述する。
002var click = function () {
003
004 //変数を宣言
005 var count = 0 ;
006
007 //関数を宣言
008 return function ( ) {
009 count ++ ;
010 alert ( "run" + count ) ;
011 }
012}
013var a = click();
014</script>
click()関数を変更した。 先ほどはclick()関数の中でrun()関数を定義していた。 今回、run()関数は定義していない。 8行目のreturnキーワードの後ろで、関数を定義している。 関数は11行目までである。 先ほどは「run」というように、関数名をつけて関数を定義した。 こんかいは、関数名をつける事無く関数を定義して、 そのままclick()関数の戻り値として設定する。 このように短く記述する事もできる。
少しだけ実践的なサンプルプログラム
002<html lang="ja">
003<head>
004<script>
005window.onload = function () {
006 const btn = document.getElementById ("btn") ;
007 btn.onclick = factory ( 0 );
008}
009const factory = function ( i ) {
010
011 var count = i ;
012 const p = document.getElementById ("count") ;
013
014 //ボタンが押下された時の処理
015 return function ( ) {
016 p.textContent = count++ ;
017 }
018}
019</script>
020</head>
021<body>
022 <input type="button" value="click" id="btn" />
023 <br/>
024 <p id="count"></p>
025</body>
026<html>
プログラムを実行すると、「click」ボタンが表示する。
clickボタンを押下すると0が表示する。
clickボタンを押下するごとに、数字が加算する。
このプログラムを製造する方法は、数多く存在する。 こんかいはクロージャを利用してプログラムを記述する。 まず、HTMLから見る。
22行目でボタンを作り、idを「btn」にする。 このボタンを押下するたびに数字を表示する。 24行目で、数字を表示するためのpタグを作る。 pタグのidは「count」に設定した。 では、JavaScriptを見る。
006 const btn = document.getElementById ("btn") ;
007 btn.onclick = factory ( 0 );
008}
009const factory = function ( i ) {
010
011 var count = i ;
012 const p = document.getElementById ("count") ;
013
014 //ボタンが押下された時の処理
015 return function ( ) {
016 p.textContent = count++ ;
017 }
018}
Web画面の読み込みが完了すると、5行目の関数が実行する。 6行目で「click」ボタンを取得して、 変数btnに代入する。 7行目でボタンのonclickプロパティに関数を代入する。 ボタンを押下すると、 ボタンのonclickプロパティに代入した関数が実行する。
onclickプロパティに代入する関数は、 factory ( ) 関数の実行結果である。factory()関数は9行目で定義している。
7行目で引数に0を渡してfactory()関数を実行すると、 11行目の変数countに「0」が入る。12行目で変数pを宣言して、 HTMLのpタグのオブジェクトを代入する。 15行目でfactory()関数の戻り値として、 関数を戻す。 戻り値の関数については後ほど述べる。
さて、factory()関数の実行結果(戻り値の関数)がボタンオブジェクトのonclickプロパティに入る。
002//ボタンが押下された時の処理
003btn.onclick = function ( ) {
004 p.textContent = count++ ;
005}
006</script>
このような形になる。このイメージ図は間違っているが、このまま解説を進める。
さて、このような状態でclickボタンを押下する。 clickボタンを押下すると、ボタンオブジェクトのonclickプロパティに代入された関数が 実行する。
002//ボタンが押下された時の処理
003btn.onclick = function ( ) {
004 p.textContent = count++ ;
005}
006</script>
4行目で、pタグのテキストに変数countの値を表示して、 その後、変数countの値を加算する。 この時、pタグも変数countも・・・無い。ない。 このイメージ図は間違っている。もう一度プログラムを戻る。
006 const btn = document.getElementById ("btn") ;
007 btn.onclick = factory ( 0 );
008}
009const factory = function ( i ) {
010
011 var count = i ;
012 const p = document.getElementById ("count") ;
013
014 //ボタンが押下された時の処理
015 return function ( ) {
016 p.textContent = count++ ;
017 }
018}
7行目でボタンのonclickプロパティに、factory()関数の戻り値を設定する。 factory()関数の戻り値は15行目の関数である。 この時、関数が参照する11行目の変数countと変数pの値も 関数と共に存在する事になる。
002btn.onclick = {
003
004 var count = 0 ;
005 const p = document.getElementById ("count") ;
006
007 //ボタンが押下された時の処理
008 function ( ) {
009 p.textContent = count++ ;
010 }
011}
012</script>
ボタンを押下すると、8行目の関数が実行する。 8行目の関数で変数countと変数pを利用するが、 それは4行目と5行目の変数を意味する。
関数は、関数内から参照する変数(変数countと変数p)とセットで存在する。
ボタンを押下すると9行目が実行して、pタグに変数countの値「0」が表示する。 その後、変数countの値が「1」になる。
002btn.onclick = {
003
004 var count = 1 ;
005 const p = document.getElementById ("count") ;
006
007 //ボタンが押下された時の処理
008 function ( ) {
009 p.textContent = count++ ;
010 }
011}
012</script>
4行目のように、「1」が入る。 ボタンを押下するたびに変数countの値がpタグに表示し、 そして変数countの値が加算される。 プログラムの解説は以上である。
面白いのは、外部から変数countの値を変更できない点である。
まとめ
クロージャ 関数とその環境がセットになったもの