プログラミングをおこなっている方でも理解するのが難しいクロージャの意味や使い方、注意点などについて解説し、クロージャを使うメリットやデメリット、コード例についても紹介します。
クロージャは、まとまった処理を定義できる構造のことで、関数とその関数が定義されている状態がセットになっているものです。
クロージャの概念や仕組みを理解することは難しいため、完璧に理解している方は少ないのではないでしょうか。本記事では、クロージャの意味や使い方、メリットやデメリットなどについて解説します。
目次
クロージャとは関数とその関数が定義されている状態をセットにしたもので、以下のJavaScriptのような変数に変数が入っている状態を指します。
var createTimer = function () { var time = 10; return function timeDown() { time -= 2; console.log(time); }; };
また、クロージャは関数内の状態を外部から変更できない状態にするという性質上、関数を定義した状態でスコープが決定するレキシカルスコープの言語でしか発生しないオブジェクトです。スコープとは、宣言した変数やオブジェクト、関数などの呼び出せる範囲ことです。
レキシカルスコープのプログラミング言語には、「JavaScript」や「Ruby」、「Python」が当てはまります。
クロージャを使わない関数は、変数内部に変数が入っていないため、結果が変わることがありません。ここでは、クロージャを使わないJavaScriptの関数例を解説します。
function timeDown() { var time = 10; time -= 1; console.log(time); } timeDown(); // 「9」 timeDown(); // 「9」のまま timeDown(); // 「9」のまま 実行結果 9 9 9
このクロージャを使わない関数では、timeDown()が呼ばれても変数(time)は9のままで、呼び出されるたびに、変数(time)で10が初期化され-1されるので、得られる結果は9のままです。
クロージャを使ったJavaScriptの関数では、以下のようになります。
var createTimer = function () { var time = 10; return function timeDown() { time -= 2; console.log(time); }; }; //グローバル変数にセットされたことで、クロージャになる var timer = createTimer(); timer(); // 「8」 timer(); // 「6」 timer(); // 「4」 実行結果 8 6 4
クロージャを使った関数の場合、関数(createTimer())の中で定義されている変数(time)と関数の結果がセットで保存されているので、timer()が実行されるたびに結果が変化します。
クロージャは、関数と変数の結果がセットで保存されている状態のことです。そのため、処理結果がリセットされないため、以上のような結果になります。
クロージャを使うことによって、どのようなメリットがあるのでしょうか。具体的にはクロージャを使うことによって、主に以下の3つのメリットが得られます。
ここでは、クロージャのメリットをそれぞれ解説します。
クロージャを使う基本的なメリットとして、コードの記述を省略できることがあげられます。クロージャは、関数があらかじめセットされているので、所定のタイミングで呼び出すことにより、実行するまでの時間を省略できます。同じ処理をおこなうコードを繰り返し記載する必要がなくなるため、全体的に記述の省略が実現できるのです。
また、所定のタイミングで呼び出す関数をコールバック関数と呼びます。ユーザーによるクリックやタイマーなどのアクションは、コールバック関数で指定することがほとんどです。
クロージャを使うことの大きなメリットは、カプセル化ができることです。
必要な情報のみを外部に公開することをカプセル化といい、オブジェクト指向をサポートするプログラミング言語で用いられることが多いです。クロージャは、関数とスコープがひとまとめになったものであるため、呼び出し元のスコープと異なるスコープが設定されます。
そのため、関数が定義されていた場所にある変数は呼び出し側から見えないようになっているのです。
カプセル化することで外部から、変数を変更できないように設定できますが、クロージャ内部で引数を設定することにより、処理内容を変更したり、対象を選択したりできます。
たとえば、違う引数で同じ処理をおこなう関数を生成したい場合は、クロージャ内に2つの引数を渡すことで実行可能です。
また、変数を外部から変更できない部分と変更可能な公開部分のそれぞれを指定可能なため、関数のカスタマイズ性も高くできます。
クロージャには、さまざまなメリットがありましたが、その反対にデメリットも存在しています。主なデメリットは、以下の2つがあります。
ここでは、クロージャを使うことのデメリット2つを、それぞれ解説します。
クロージャを使うことのデメリットは、メモリ管理上の問題が起こることがあるということです。
クロージャのスコープ内にある変数を外部から参照されている場合、オブジェクトが互いに参照しているため、クロージャが破棄されてもメモリが解放されません。
このように、メモリの解放順番が決められず、いつまでもクローズの解放がおこなわれないと、メモリを大量に消費してしまいます。
クロージャ自体のデメリットではありませんが、一部コードが理解しにくいこともデメリットとなりえます。
「Swift」でクロージャを扱う場合、returnや戻り値、関数などを省略して表記するケースがあります。この省略の部分については、エンジニアの裁量に任される部分であるため、同じシステムであったとしても、人によってコードが異なる可能性があるのです。
そのため、コーディングに慣れるまでは、コードの読み取りに時間がかかってしまうケースがあります。
JavaScriptでクロージャを使うためにいくつか理解しておくべきことがあります。
ここでは、以上の3つのやり方について解説します。
クロージャをカプセル化する場合、以下のように記述します。
window.onload = function () { // クロージャによるカプセル化の書き方 // オブジェクトの定義 var Hello = function (_name, _major) { // カプセル化されたプロパティ(変数) var name = _name; // 公開されているプロパティ(変数) this.major = _major; // カプセル化されたメソッド var getName = function () { return name; } // 公開されているメソッド this.setName = function (_name) { // 代入前に加工ができる name = 'Ms.' + _name; } // 公開されているメソッド this.getMajor = function () { return this.major; } // 公開されているメソッド this.say = function () { console.log('Hello! ' + getName() + '. I know you are great about ' + this.major + '!'); } } }
varを使ってメソッドを変数として代入しているところがポイントのひとつです。以上のよう記載することで、カプセル化して外部に公開されないメソッドにすることが可能です。
コールバック関数を使う場合、以下のように記述します。
setTimeout(function() { console.log('HelloWorld!'); }, 4000);
上記はsetTimeout関数で、指定した秒数後にコードバック関数を受け取り実行します。上記サンプルコードの場合、2行目以降の関数を受け取り、4秒後にHelloWorld!を表示します。
最後はクロージャを実践的に使う場合の記述方法です。onlandと組み合わせて使う場合は、以下の記述になります。
// クロージャによるイベントハンドラの書き方 // onloadにクロージャを設定し使う方法 function makeOnLoadCallback(name) { var cnt = 0; // クロージャを戻り値にする return function () { cnt++; console.log('onload event named as "' + name + '" fired! at ' + cnt + ' times.'); }; } window.onload = makeOnLoadCallback('myOnLoad'); 実行結果 onload event named as "myOnLoad" fired! at 1 times.
上記で紹介した記述では、onloadにmakeOnLoadCallback関数を定義しています。
onclickと組み合わせて使う場合の記述
window.onload = function () { // クロージャによるイベントハンドラの書き方 // onclickにクロージャを設定し使う方法 function makeOnClickCallback(msg) { // メッセージ var msgStr = msg.toString(); // クロージャを関数オブジェクトとして変数に代入 var closure = function () { // クロージャの処理 console.log('Alert! You are warned as "' + msgStr + '". '); } // クロージャを戻り値にする return closure; } // クリックするエレメント var el = document.getElementById('log'); // onlickイベントハンドラにクロージャを登録する el.onclick = makeOnClickCallback("ただいま手が離せません"); } 実行結果 Alert! You are warned as "ただいま手が離せません". Alert! You are warned as "ただいま手が離せません". Alert! You are warned as "ただいま手が離せません".
クロージャをSwiftで使う場合も、JavaScriptと同様にいくつか理解しておくべきことがあります。
主に以上の3つを理解しておくことで、Swiftでクロージャを使えるようになります。
ここでは、それぞれの使い方を解説していきましょう。
Swiftでのカプセル化は、以下のように記述します。
func makeCounter(initialValue: Int) -> (()->Int) { var count = initialValue; // この部分の変数を隠す func countDown() -> Int { count -= 1; // 変数にアクセスできる return count; } return countDown; // 関数のみ公開する } let f = makeCounter(initialValue: 4); while (f() > 0) { print("計測中"); } print("完了"); 実行結果 計測中 計測中 計測中 完了
上記のようにカプセル化することで、変数を隠すことや内部からアクセスできます。
Swiftでクロージャを使う場合、型として使うためには、以下のように記述します。
let closure:(Int,Int) -> String closure = { (num1: Int, num2: Int) -> String in return String(num1 + num2) } closure(10,20) // "30"
クロージャの型を指定した変数では、以下のようになります。
func changeNum(num1:Int,num2:Int) -> String{ return String(num1 + num2) } let closure:(Int,Int) -> String closure = changeNum closure(10,20) // "30"
以上の記述で、同じクロージャもしくは関数を格納することが可能です。
コールバック関数を使う場合は、以下のように記述します。
import Foundation func waitTimer(seconds: TimeInterval) { // 引数はスコープ内にある func timerCallback(_: Timer) { print("\(seconds)秒……"); } _ = Timer.scheduledTimer(withTimeInterval: seconds, repeats: false, block: timerCallback); // コールバック関数を登録 } waitTimer(seconds: 1); waitTimer(seconds: 2); waitTimer(seconds: 3); print("タイマーセット完了。"); RunLoop.current.run(until: Date(timeIntervalSinceNow: 4)); // タイマーを動かす 実行結果 タイマーセット完了 1.0秒…… 2.0秒…… 3.0秒……
プログラミングを書いているとメモリリークが起きてしまうコードを書いてしまうことがあります。メモリリークしてしまうと、メモリの空き容量がなくなってしまいサーバーやパソコンがダウンしてしまう可能性があります。
以下のコードでは、メモリリークしてしまいエラーが起きている記述です。
window.onload = function () { // メモリリークする場合のクロージャの書き方 // メモリリークする変数を参照するオブジェクト function makeRefHavingClosure(reference) { // 変数の参照を持ち、メモリリークする原因となるクロージャ this.log = function () { console.log('leak val is ' + reference.toString()); } } // 非常に大きなリークするデータ var bigData = "リークしてメモリを消費するデータ"; // インスタンス化 var ins = new makeRefHavingClosure(bigData); delete bigData; ins.log(); } 実行結果 leak val is リークしてメモリを消費するデータ
上記の場合、delete bigDataでデータを消去する予定ですが、残念ながらクロージャがbigDataを参照しているためメモリ上では削除されません。このような記述とならないように注意が必要です。
クロージャを使うことで記述の省略やカプセル化などのさまざまなメリットがあり、JavaScriptやSwiftでは関数がクロージャとしての性質を持っています。
また、クロージャを覚えておくことでプログラミングが便利になるため、クロージャの使い方や意味、性質を理解して関数を使いこなせるようにすることがおすすめです。
これからクロージャについて学習する方やクロージャを使っていこうと思っている方は、ぜひ本記事を参考にしてみてください。
2024.06.17
子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介
#プログラミングスクール
2022.01.06
【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説
#プログラミングスクール
2024.01.26
【未経験でも転職可】30代におすすめプログラミングスクール8選!
#プログラミングスクール
2024.01.26
初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説
#JAVA
2024.01.26
忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説
#プログラミングスクール
2022.01.06
【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介
#プログラミングスクール
2024.01.26
【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説
#プログラミングスクール
2024.01.26
【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説
#プログラミングスクール