慧音です。
あたいはチルノだよ!
早速だが、今回はクロージャについての勉強をしよう
え、もう意味わかんないんだけど。
遊びにいってもいい?
後で頭突きな。
いきなりクロージャ!と言われてもわからんだろう
そこで、だ 順を追ってゆっくりとやっていけばおのずと理解できると信じている!
今回は3つに分けて説明をしていくぞ
1:関数内の関数である!
2:無名関数である!
3:関数を返す関数である!
関数の中の関数?
そうだ。関数の定義した中でさらに関数を定義しているもの、だ
つまり、クロージャとは関数であり
クロージャとは、関数の中の関数なのだ!
関数って文字がゲシュタルト崩壊するよ…
では関数の中の関数とはどういう事なのか、ソースを見てみる事にしよう
function outer(){
function inner(){
alert("hello");
}
}
うん、確かに関数の中に関数が入っているね
でもこれってどうやって動かすの?
いい疑問だな!じゃぁ考えてみろ!
あたいをナメてもらっちゃ困る。こんなのらくしょーよ!
function outer(){
function inner(){
alert("hello");
}
}
inner();
ふむふむ。では実際に動かしてみようか
あれ?なんか動かないし、エラーが出たよ?
そうだな。これは間違いだ。
チルノが指定した関数innerは、outerの中に内包されているわけで、どこでも使えるものではないのだ
ローカル変数とグローバル変数と似た考え方だ。
ローカル変数をグローバル上で読みだす事はできなかっただろ?
そうね。これはけーねを試したのよ。本当の答えはこれよ
function outer(){
function inner(){
alert("hello");
}
}
outer();
うむ。グローバルしか読み込めないのでそうだな。ではまた動作を見てみよう
エラーは出ていないようだけど…何もでないね
確かに呼び出し方は問題がなかった。しかし、呼び出した関数outer内の処理をよく見てみろ
inner関数を生成しているだけで何もしようとしていないだろう
つまり、何かをできるように準備しているが、何もしようとしない、と言った感じか。
明日から本気だす
ほぼ正解だな。
準備すらしていない者もいそうだが
もう答えは?ギブアップだよ
これが正解だ
function outer(){
function inner(){
alert("hello");
}
inner();
}
outer();
一応動作も確認しておこうか
おー、出た出たー!へろー
処理の順番としてはこうだ
1:outer関数実行
2:inner関数宣言
3:innre関数実行
4:inner関数内のalertが実行
5:こんちわ。
なるほど、決められた場所で実行してあげないと動かないんだね。
うむ。その通りだ。よし続けていくぞ
無名関数?名前なし?大ちゃん?大ちゃん関数だね
大ちゃん泣くぞ
無名関数とは!その名の通り、関数に名前がないのだ
名前が無かったら使えないじゃん
その通りだ。しかし無名関数を使う方法もある
通常の関数と無名関数の違いを見てみよう
//名前のある関数。
function nameName(){
alert('こんちゃーす');
}
//名前の無い関数
nameName = function(){
alert('こんちゃーす');
}
似てるけど微妙になんか違うね
そうだな。主な違いは
1:function と () の間に文字列があるとない。
2:functionから始まってるものは、変数に入ってる。
が違う。
しかし、呼び出し方法はどちらも同じで
nameName(); で実行されるのだ
え?一緒!?これ意味あるの?
正直どっちでもいいような気はする
しかし、無名関数の場合、名前を使わないで作れるので一応利点はあるのだ
無名関数は宣言と同時に実行する事が可能だ
//名前のある関数。
function nameName(){
alert('こんちゃーす');
}
nameName()
//名前の無い関数
nameName = function(){
alert('こんちゃーす');
}
nameName()
//nameName()で呼び出さなければならないが
(function () {
alert('こんちゃーす');
})();
//で宣言と同時に呼び出しが可能となる
おー、こんなので動くんだねー
これが無名関数と言ったところだ。
使い方は大丈夫だな?次へいくぞ
関数を返す関数!関数の中の関数!関数関数!!!あたい関数って言葉はもうお腹いっぱいだよ!
こればっかりはな…これもそのままの意味なんだ。
実際にはこんな感じだ
function outer(){
var inner = function (){
alert('押忍');
}
return inner;
}
var go = outer();
go();
確かに動いてはいるんだけど…
でも varって変数の宣言だったよね…気持ち悪い
変数は関数でもオブジェクトでもなんでも入る魔法の箱だ
関数が入ればそいつは変数ではなく関数に化ける事ができるのだ
元々その魔法の箱は変数であるので、関数を入れた後、適当なデータを入れる事が出来る。そうすると
また変数に戻る。上書きされるのはわかるな?
変数で関数で…うーん、難しいね
まぁ、慣れていけばいいんだろうけど…
でもコレってどういう動きになってるの?
そうだな、この動きを例えるならば
function オッサン(){
var inner = function (){
alert('押忍');//押忍って言えるにはこうするって知識
}
return inner;// 教えれるんだけどな。聞かれたら
}
var 若者 = オッサン(); // 若者「オッサン!押忍と言える手法…俺にくれ!!!」 オッサン「いいですとも!」
若者(); //若者「押忍」
余計混乱しそう
まぁ…おのずとわかるハズだ…
この書き方だが、違う書き方で同じ動きをする事が可能だ
function outer(){
function inner (){
alert('押忍');
}
return inner;
}
var go = outer();
go();
んー、無名関数で変数に入れているんじゃなくて
普通に変数宣言して、変数名を返しているって事かな?
そうだ!その通りだ!やるじゃないか!
しかし、これだけではない、まだ別の方法があるぞ
function outer(){
return function (){
alert('押忍');
};
}
var go = outer();
go();
返す時に関数作ってる!なんてせっかちな野郎だ!
普通に動いてる!
変数に入れる、または、関数名を付けてあげる
の動きを省いたものだ。
結局は同じ処理を渡しているので、結果は同じなんだ
まぁ、書き方はこの3パターンだ。
ちょっとソースを変更してみよう。
alertにベタで書いていたのを変数から読み取るように変えるぞ
function outer(){
var inner = function (){
var x = '押忍'
alert(x);
}
return inner;
}
var go = outer();
go();
これはそりゃでるよね。あたいったら理解力さいきょーね
よしよし、じゃぁ次は変数xの位置を変えてみるぞ
function outer(){
var x = '押忍'
var inner = function (){
alert(x);
}
return inner;
}
var go = outer();
go();
うん、outerの中だし、関数内でグローバル変数が読めるのと同じ感じかな?
そうだそうだ。
チルノ、これがクロージャだよ
はぁ!?けーね!説明省きすぎじゃないの!?
説明がめんどくさくなったから手抜いてもこれくらいなら手抜いてるってあたいでもわかるよ!
そういうのも無理はない。これがクロージャなんだからな…
決して手を抜いているわけではないぞ
まぁ、これまでのサンプルで完璧に理解するのも難しいだろう
もうちょっと分かりやすい例でやっていこう
もう一度おさらいをすると
1:親関数のスコープ内で変数を定義し、
2:親関数の中に子関数(=関数内関数)を作って
3:その子関数から、先ほどの親関数内の変数を参照する
ってことだ
で、結局の所どう使うの?
それではさっきのサンプルに手を加えてみよう
function outer(){
var x = 1
var inner = function (){
alert(x);
x = x + 1;
}
return inner;
}
var go = outer();
go();
go();
go();
なんだ、ただ足し算しているだけじゃん…
って!!!数字増えてるじゃん!なんで!?
いい反応だ!
つまりクロージャとは、状態を保持する関数 を生成する事ができるって事だ。
どことなくオブジェクトに書き方が似ているね
順を追って説明するぞ。
1:outerという関数がある
2:outer関数内では、まず変数xを宣言し、1を入れている。
3:そして変数innerに無名関数を設定する(子関数)
4:無名関数の中は、outer内(親)で宣言した変数xをまずalertし
5:親で宣言した変数を直接、現在の親変数内の数値+1 した結果で上書きを行う
6:outerを実行した時には、子関数を返す
7:変数goに対して、outerの戻り値(この場合だと、子関数のinner)を入れる
8:この時点で、go=innerとなるわけだ
9:実際にgoを実行させてみる
10:親変数xは1なので、alert("1")が画面上に表示
11:1+1の結果を親変数xに入れる!2になるな。
12:ここがキモだ。続けてgoを実行すると、1+1の結果が残った状態となり、alert("2")が画面上に表示
13:そして、2+1の結果が親変数に入る
14:後は一緒だ。3が出て、最終的に親変数に4が入って終わりだ。
けーね。長い
なんで数値が残ってるのさ。
var x = 1;はどこいったの
だいぶ混乱してきたな
var go = outer();
の時点で、goの中身は
function (){
alert(x);
x = x + 1;
}
となっている。
つまり、goは
変数xをalertして
足し算しているだけ
の動きになるのはわかるな?
そうだね。関数innerの中身だけを返しているからね。
でも、xの値は?宣言ないからundefinedじゃ?
xの値は、var go = outer();の時点で
関数outerが実行されているので、一番最初にx=1;が動いているだろう。
で、innerはxは親関数(outer)で宣言されたxだから1だね。って理解しているんだ
そして理解したまま、渡されるから最初のgo()時点で、alertも1が出るわけだ
後はそのまま足し算していくだけだな。
ほら、アニメーションとかの時、動かすたびに変数がリセットされているわけではなく
きちんと保持しているだろ?後はそこと同じ考え方でいいんじゃないかな
あー、アニメーションのやつはグローバル変数でフラグ用変数にデータ入れて
それを子関数で認識して、それを使いまわして、trueの場合は動かすとか動かさないとか
監視できてたね。初期値設定は最初だけ動いて、関数内でその値を保持して使いまわしてやってるね。
その通りだと思うぞ
最初からそれでいいじゃん!!!説明
細かい事はなんとやら、だぞ
とにかく、さっきおさらいしてた
1:親関数のスコープ内で変数を定義し、
2:親関数の中に子関数(=関数内関数)を作って
3:その子関数から、先ほどの親関数内の変数を参照する
に追加して
4:参照して内容を理解したまま関数内関数が渡されるから、値を使い回しできる
って事で大丈夫かな
まぁ、それで大丈夫だと思う!
ダメなら直せばいいんだ!直せば!
メタってる