概要
CoffeeScriptのコードには、jQueryの利用時を含めて以下のような無名のコールバック関数の記述が頻繁に現れる。
1 2 |
$ -> $("p").css("color", "blue") |
これについて、改めて整理してみた。
無名関数のクロージャ
CoffeeScriptの以下のコードを考える。
1 2 |
-> console.log("closure") |
これをコンパイルしたJavaScriptのコードは以下のようになる。
1 2 3 4 5 6 7 |
// Generated by CoffeeScript 1.10.0 (function() { (function() { return console.log("closure"); }); }).call(this); |
“->”以下インデント部分が一つの関数とみなされ、無名関数として定義されている。
コールバックの登録
生真面目な書き方
無名関数をコールバック関数として、呼び出し元の関数の引数で登録するのに、生真面目に引数を()で括って書くと以下のようになる。
1 2 3 4 5 6 7 8 9 10 11 |
caller = (callback) -> console.log("before call") callback() console.log("after call") caller(-> console.log("called")) # 実行結果 # before call # called # after call |
実行結果は意図したとおり、呼び出し元の中でコールバックが呼ばれている。
これをコンパイルしたJavaScriptコードは以下の通りで、無名関数が引数として渡されている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Generated by CoffeeScript 1.10.0 (function() { var caller; caller = function(callback) { console.log("before call"); callback(); return console.log("after call"); }; caller(function() { return console.log("called"); }); }).call(this); |
()を省略した書き方
CoffeeScriptでは関数の引数を()で囲わずに書けるので、以下のように書ける。
1 |
caller -> console.log("called") |
実行結果は変わらず、これをコンパイルしたJavaScriptコードも上と全く同じ。
引数の改行~失敗
CoffeeScriptはかなり自由に改行することができるので、先のコードの引数を”->”の直後で改行してみる。
1 2 3 4 5 6 7 |
caller -> console.log("called") # 実行結果 # before call # after call # called |
結果は想定外で、呼び出し元でコールバックが実行されていない。
JavaScriptへのコンパイル結果は、以下のように少し変化している。
1 2 3 |
caller(function() {}); console.log("called"); |
想定外の動作の理由は、”caller ->”の次のconsole.log()の行が前の行と独立した命令として解釈されたため。
- 呼び出し元に内容がない無名関数”->”が渡される
- コールバック関数は内容がないため無反応
- console.log()が実行される
引数の改行~インデントが必須
ここでconsole.log()の行をインデントすると、console.logの行が関数callerの内容と解釈されるため、console.log()は無名関数”->”の実行内容とみなされる。
1 2 3 4 5 6 7 |
caller -> console.log("called") # 実行結果 # before call # called # after call |
JavaScriptコードも以下のように意図した結果になった。
1 2 3 |
caller(function() { return console.log("called"); }); |
引数の改行~別の想定外
コールバックを引数として()で括ったうえで改行した場合、また異なる結果となる。
1 2 3 4 5 6 7 |
caller(-> console.log("called")) # 実行結果 # called # before call # after call |
コンパイル結果を見ると一目瞭然で、
1 |
caller(function() {}, console.log("called")); |
先のCoffeeScriptコードと等価なコードは以下の通りで、上のJavaScriptと同じコードにコンパイルされる(“->”を()で括らないと”想定外のcomma”エラーになる)。
1 |
caller((->), console.log("called")) |
呼び出しの際に定義された引数の数より多く引数を渡しても過剰な引数は渡されないが、その引数が実行可能な文の場合には実行される。
この例ではcosole.log()は引数として渡されないが、呼び出し元の関数実行前にconsole.log()文として実行され、その後に内容なしの無名関数をコールバックとして呼び出し元が実行されるため、意図とは異なる結果になる。
なお()をつける場合でも、インデントによって意図した結果となる(最後の”)”はconsole.log()文の末尾につけてもok)。
1 2 3 4 5 6 7 8 |
caller(-> console.log("called") ) # 実行結果 # before call # called # after call |
複数行のコールバック
複数行のコールバックは、各行のインデントを揃えて書く(インデントのレベルが深いと、その行で”unexpected indentation”エラー)。
1 2 3 |
caller -> console.log("called") console.log("really called") |
内容の一部だけを改行するとエラー。
1 2 |
caller -> console.log("called") console.log("really called") # unexpected indentation |
引数を持つコールバック
コールバックが最後の引数の場合
以下のように、最後の引数のコールバック関数をインデント付で改行すればok。処理内容が複数行でもok。
この形は、jQueryのイベントハンドラ・アタッチメントに出てくる。
1 2 3 4 5 6 7 8 9 10 11 12 |
caller = (arg, callback) -> console.log("argument is '#{arg}'") callback() console.log("after call") caller "event", -> console.log("called") # 実行結果 # argument is 'event' # called # after call |
コールバックが最後以外の引数の場合
これは”unexpected ,”エラー。
1 2 3 4 |
caller = (callback, x) -> console.log(callback(x)) caller (x)-> x * x, 3 |
無名関数を()で括ると意図通り実行。
1 |
caller ((x)-> x * x), 3 |
これもok!
1 2 |
caller ((x)-> x * x) , 3 |
敢えてこう書けるが、段々ややこしくなる。
1 2 3 |
caller ((x)-> x * x) , 3 |
残念ながら()なしはだめ。
1 2 3 |
caller (x)-> x * x , 3 |