IORI online School

JavaScript、html、css の無料学習サイト

【JavaScript 中級講座】クロージャの学習

[JavaScript 中級講座]クロージャの学習

本講座では、クロージャを学習する。 サンプルプログラムを3つ掲載した後、 (少しだけ)実践的なプログラムを見る。

クロージャを理解するための準備段階

001<script>
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()関数を実行する。

002//変数を宣言
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」と表示する。

001<script>
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」と表示する。 解説は以上である。

関数の中から、関数の外の変数を利用できる

このサンプルプログラムで、 関数内から関数外の変数を参照できる事を学習した。 もう一つサンプルプログラムを見る。

001<script>
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行目のような)宣言した同じ場所からでも 変更可能である。

はじめてのクロージャ

001<script>
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()関数の処理を見る。

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}

まず、5行目で変数countを宣言して「0」を代入する。 8行目で、run()関数を定義する。 click()関数の中にrun()関数を定義する形である。 さて、ここでは関数を定義しているだけなので、 run()関数が実行する事は無い。12行目で、run()関数を戻り値に設定している。

関数を実行する場合は、関数名の後ろに括弧を記述する。 12行目のrun()関数は、関数名「run」の後ろに括弧が記述されていない。 つまり、ここでrun()関数が実行するわけではない。 戻り値としてrun()関数を指定しているだけである。 click()関数を実行すると、実行結果としてrun()関数が戻る。

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 ();

14行目でclick()関数を実行すると、戻り値としてrun()関数が戻る。 そのrun()関数を変数aで受け取る。 変数aはrun()関数を参照する。 run()関数を参照するだけであり、ここでrun()関数を実行するわけではない。 17行目でaに代入されたrun()関数を実行する。 変数名の後ろに括弧を記述すると、 変数に入っている関数を実行する。 ここで、run()関数が実行する。

007  //関数を宣言
008  function run () {
009    count ++ ;
010    alert ( "run" + count ) ;
011  }

変数aに入っているrun()関数である。この関数が実行する。 では、解説する。まず、9行目で変数countを・・・、 変数countを・・・。変数countが無い?

クロージャ 関数とその環境(関数内で利用する変数)がセットになったもの

クロージャを解説する。 こんかい、run()関数で変数countを利用するが、 そもそもrun()関数内に変数countが存在しない。 変数countとは何か?

変数countは、click()関数が実行した時に宣言した変数である。

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}

click()関数を実行した時、5行目で変数countを宣言している。 click()関数が実行すると、変数countに「0」が代入される。 その後、変数countはclick()関数内で利用されることなく、 click()関数の処理は終了する。

click()関数の処理が終了した時点で、変数countは消えるはずである。 しかし他の関数、この場合はrun()関数に参照されているため、 run()関数と同じ環境で残る。 消えない。

簡単な話、「関数から参照される外部変数は関数と共に存在します」と言う事である。 つまりrun()関数は、変数countとセットである。

001 {
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」と表示する。解説は以上である。

つぎに、今のプログラムを別の方法で記述する。

001<script>
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()関数の戻り値として設定する。 このように短く記述する事もできる。

少しだけ実践的なサンプルプログラム

001<!doctype html>
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

clickボタンを押下すると0が表示する。

click
0

clickボタンを押下するごとに、数字が加算する。

click
10

このプログラムを製造する方法は、数多く存在する。 こんかいはクロージャを利用してプログラムを記述する。 まず、HTMLから見る。

022  <input type="button" value="click" id="btn" />
023  <br/>
024  <p id="count"></p>

22行目でボタンを作り、idを「btn」にする。 このボタンを押下するたびに数字を表示する。 24行目で、数字を表示するためのpタグを作る。 pタグのidは「count」に設定した。 では、JavaScriptを見る。

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}

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プロパティに入る。

001<script>
002//ボタンが押下された時の処理
003btn.onclick = function ( ) {
004  p.textContent = count++ ;
005}
006</script>

このような形になる。このイメージ図は間違っているが、このまま解説を進める。

click

さて、このような状態でclickボタンを押下する。 clickボタンを押下すると、ボタンオブジェクトのonclickプロパティに代入された関数が 実行する。

001<script>
002//ボタンが押下された時の処理
003btn.onclick = function ( ) {
004  p.textContent = count++ ;
005}
006</script>

4行目で、pタグのテキストに変数countの値を表示して、 その後、変数countの値を加算する。 この時、pタグも変数countも・・・無い。ない。 このイメージ図は間違っている。もう一度プログラムを戻る。

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}

7行目でボタンのonclickプロパティに、factory()関数の戻り値を設定する。 factory()関数の戻り値は15行目の関数である。 この時、関数が参照する11行目の変数countと変数pの値も 関数と共に存在する事になる。

001<script>
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」になる。

001<script>
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の値を変更できない点である。

まとめ

クロージャ 関数とその環境がセットになったもの