Home > JavaScript > JavaScript はじめてのクロージャ入門

JavaScript はじめてのクロージャ入門

JavaScript はじめてのクロージャ入門
というタイトルの割には、クロージャについてあんまり深く突っ込んでないなー。

問い 以下の結果が返ってくる関数を作りなさい。


var tanaka = generateAgeCounter('1984-06-24');
console.log(tanaka('2008-02-18')); // => 23
console.log(tanaka('1999-07-31')); // => 15

会社の先輩からクロージャを理解するため(かどうかは分からないけど)
にこのような問題を出して頂いたので考えてみる。

これは変数が参照している誕生日から、
ある時点で何歳かどうかを調べるというもの。

クロージャを使わないで、このプログラムを作るとすれば
今までの自分の考え方だと単純に引数を2つ持たせることを考えてたと思う。

が、このプログラムでは引数はひとつしか持っていない。
クロージャを理解するためにちょうどいいような問題なので考えてみる。

まずクロージャを理解するために色々サイトを眺めてみる。

クロージャ - Wikipedia

典型的には、クロージャはある関数全体が他の関数(以下、エンクロージャ)の内部で 宣言されたときに発生し、内部の関数はエンクロージャのローカル変数(レキシカル変数)を参照する。 実行時に外部の関数が実行された際、クロージャが形成される。 クロージャは内部の関数のコードとエンクロージャのスコープ内の必要なすべての変数への参照からなる。



↓ここからエンクロージャ(クロージャを包み込む関数)
function a() {
var str = 'enclosure_local';
//↓ここからクロージャ
function b() {
return 'Hello' + str;//エンクロージャのローカル変数(レキシカル変数というらしい)strをクロージャによる参照
}
return b();
}



JavaScriptの巧い書き方

また、匿名関数を使ってスコープを一段下げてやることで、関数内のローカル変数をグローバル変数のように扱うこともできる。この辺はクロージャでよく使います。

参照している変数は、クロージャをつくった関数の終了後も存続する



function(){ // 匿名関数でスコープを一段下げる
var msg = "sage"; // msgはグローバル変数ではない
window.onunload = function(){ // グローバルオブジェクトwindow.onloadに関数を付加
alert( msg ); // msgはクロージャによる参照
} // close function
}();



function newCounter() {
var i = 0;
return function() { // anonymous function
i = i + 1;
return i;
}
}

c1 = newCounter();
alert(c1()); // 1
alert(c1()); // 2
alert(c1()); // 3


function foo(x) { return x }
var foo = function(x) { return x }



JavaScript

関数定義はクロージャの変数への束縛にすぎない。次の2つはほぼ同義。

括弧をつけてfoo(1)とすれば関数適用だが、fooと名前だけ書くと変数として参照する。

クロージャであるから当然次のようなコードも可能。



function make_counter() {
  var x=0
  return function() { return x++; }
}
var c = make_counter()
alert(c()) // 0を表示
alert(c()) // 1を表示


なんとなく分かってきた気もするけど、
完全には理解できてない。。

とりあえずここまで見てきたプログラムの動きを真似して作る。



function diffYearCounter(year){
var current = year;

return function(diff_year){
return current + diff_year;
}
}

var counter = diffYearCounter(2000);
counter(8); //2008
counter(16); //2016

動いた。
これこそが今回したかったことで、
currentの値を参照して、その値の差を足すことができた。

ということでこれをベースに作ってみて、
できた!と思ったら微妙にうまくいかない。


var tanaka = generateAgeCounter('1980-07-15');
alert(tanaka('2000-07-14'));

こういうので一日早いのに20歳になってしまう。
ちょっと考えてたら、うるう年に気がつく(笑)
うるう年か。

というかこのあたりからクロージャ一切関係ないけど
getTimeでDateオブジェクトにしてそれをひいた差分で
でるかな?と思ってたけどそうではないみたい。

生れてから今日まで、何日生きてきただろう!

↑このサイトに詳しく書いてあり、なるほど
と思い参考にしようと思っていると、

生年月日から年齢を計算する簡単な計算式

こういうのを見つけてしまった。

読者の方から「生年月日から年齢を計算する簡単な計算式」はこの式はこのままで使えるとは限らないというご指摘をいただきましたので追記させていただきます。

「誕生日の前日が終了する瞬間(すなわち誕生日をむかえる午前0時00分の直前)に1歳を加えることになる」の部分の解釈には前日に1日加えると解釈される場合と当日に1日加えると解釈される場合の2種類があるようです。特に自治体においては前者で解釈される場合が多いとのことです。大変重要なご指摘でしたので追記させていただきました。

うーん、よく分からないけれどとりあえず試した限り
いい感じで動く。(といういい加減なことを言っているとダメだと思う)
ただ、今回はどっちかというとクロージャがメインなのでよしとする。

ということでこんな感じで作った。


function generateAgeCounter(d) {
  var birth_date = getDate(d);
  
	return function(diff_d) {
		return parseInt((getDate(diff_d) - birth_date) / 10000);
	}

function getDate(date_text) {
if (String(date_text).match(/^(?:(\d{4})-|)(\d{2})-(\d{2})$/)) {
var year = RegExp.$1, month = RegExp.$2, day = RegExp.$3;
return year + month + day;
} else {
return false;
}
}
}

var tanaka = generateAgeCounter('1970-07-15');
alert(tanaka('2000-07-14')); // 29
alert(tanaka('2000-07-15')); // 30
alert(tanaka('2020-02-17')); // 49

まだまだよく分からないけれど少し分かったような気もする。
うーん、今回は引用だけという感じなのでちゃんと言葉にできるようにまとめたいなー。

Comment:0

Comment Form
Remember personal info


Home > > JavaScript はじめてのクロージャ入門

Page Top