【coffeescript】ファットアローでコンテクスト保持した関数定義【javascript】
こんばんは.1000chです.
coffeescriptの機能の中で好きなものに,「ファットアローによる関数定義」があります. 一言で言うと「定義する関数内で現在thisコンテキストを維持」することができるようになる機能です.
javascriptにおけるthisトラップはよくある話ですが,coffeescriptがどのようにこの問題を解決しているのか見ていきます.
javascriptにおける関数定義
javascriptにおける関数定義は,
- 関数式
- 関数宣言
- Functionコンストラクタ
がありますね.それぞれこんな感じです.
//関数式 var func1 = function(name){ console.log(name) } // 関数宣言 function func2(name){ console.log(name) } // Functionコンストラクタ var func3 = new Function('name','console.log(name)') //実行 func1("alice") // alice func2("bob") // bob func3("cate") // cate
どれもちゃんと動きますが,基本的には関数式が使われることが多いですね.
coffeescriptに置ける関数宣言
coffeescriptにおける関数宣言は->
によって行います.
最も簡単な例を見てみましょう.
# 定義 func1 = -> console.log "hello" # 呼び出し func1() # hello
引数を与えるときは() ->
です.
# 定義 func2 = (name) -> console.log name # 呼び出し func2("alice") # alice # 引数ある場合は,スペース入れれば()省略可能 func2 "alice" # alice # 複数行での定義もできる # ブロックはインデントで表現 func3 = (name) -> text = "my name is " + name console.log text
さて,このときどのようなコードが生成されているでしょうか.
// Generated by CoffeeScript 1.7.1 // --bareオプションでコンパイル var func1,func2,func3; func1 = function() { return console.log("hello"); }; func2 = function(name) { return console.log(name); }; func3 = function(name) { var text; text = "my name is " + name; return console.log(text); };
パターン的には,関数式を用いた関数定義が行われているようですね. 関数を格納する変数名は冒頭で一括定義するようです.
なお,coffeescriptでは明示的にreturnを書かない場合に最後に評価された内容が自動的にreturnされます.
今回の例だとconsole.log()
の行がreturnされています.
なにもreturnさせたくない場合は,関数定義の最後にreturn
とだけ書けばok.
func4 = (name) -> text = "my name is " + name console.log text return
コンパイルするとこんな感じ.
func4 = function(name) { var text; text = "my name is " + name; console.log(text); };
さらにはデフォルト引数や可変引数も使えます.便利!
func5 = (name = "alice") -> console.log name return func6 = (names...) -> for name in names console.log name return
// デフォルト値 func5 = function(name) { if (name == null) { name = "alice"; } console.log(name); }; // 可変引数 func6 = function() { var name, names, _i, _len; names = 1 <= arguments.length ? __slice.call(arguments, 0) : []; //argumentsを配列化 for (_i = 0, _len = names.length; _i < _len; _i++) { name = names[_i]; console.log(name); } };
javascriptにおけるthisコンテキストの書き換え
javascriptでは,関数の中で関数を定義すると,内側のthisコンテキストがグローバルオブジェクトを指すという仕様があります.
var myObj = { func1: function(){ console.log(this); //thisはmyObj オブジェクト (function(){ console.log(this); // thisはwindow オブジェクト })() } } myObj.func1();
これ,例えばコールバック関数を使って元オブジェクトのプロパティをいじったりするときに不都合がありますね.
そこで通常はこんな感じでコンテキストを引き渡したりします.
var myObj = { func1: function(){ console.log(this); // myObj (function(that){ console.log(that); //myObj })(this) } } myObj.func1();
本題:ファットアローでの関数定義
coffeescriptでは,コンテキストを保持した状態での関数定義が可能です.
やり方は簡単で,関数定義時に->
ではなく=>
(ファットアロー)を使うだけです.
これを用いることで,thatにthisを格納する,というような手間をかけずにコンテキスト保持ができます.
myObj = func1: () -> console.log @ do () -> #アローで関数定義,即時実行 console.log @ return do () => #ファットアローで関数定義,即時実行 console.log @ return myObj.func1()
実行結果(chrome)
Object {func1: function} Window {top: Window, window: Window, location: Location, external: Object, chrome: Object…} Object {func1: function}
coffeescriptでは@
がコンパイル時にthis
に変換されます.
また,関数定義前にdo
キーワードを置くことで,その関数を即時実行することができます.
これらを用い,関数内で->
を使った即時関数と,=>
をつかった即時関数でthis
の内容を確認しているのが上記スクリプトになります.
実行結果として,ファットアローでの即時実行ではmyObj自体を参照できていることがわかります.
ファットアロー使った場合の出力コード
さて,ではファットアローを使ったコードはどのようにコンパイルされているのでしょうか. シンプルにこんな感じで試してみましょう.
func1 = () => console.log @ return
コンパイルするとこうなります.
func1 = (function(_this) { return function() { console.log(_this); }; })(this);
ちょっとぱっと見わかりづらいかも?
まず,即時関数でthisコンテキストを_this
として渡しています.先程のthat
みたいなものですね.
即時関数の内部では無名関数を定義し,これをreturnしています.
ここで定義している無名関数こそがcoffeescriptで書いた部分であり.中では_this
を通じて
外部のthis
にアクセスしています.
要するに,やってることは先程のthat
にthis
を格納するパターンと一緒ですね.
coffee側で記述しているのは内部の無名関数部分だけですが,コンパイラが_this
を渡す即時関数部分を自動で付け加えてくれるという感覚でしょうか.
まとめ
javascript,coffeescriptにおける関数定義の方法,コンパイル後のコードの動作を確認しました. また,javascript特有のコンテキスト保持の方法を確認し,coffeesriptではファットアローを使うことで簡単に同じことができることを確認しました.
ファットアローを活用すると,ajaxのコールバック関数でスマートにコンテキスト保持ができるようになります.
# Vue.jsで応用した例 do (window,$,_,Vue) -> vm = new Vue el: "#main" data: my_list: [] ready: @func1() methods: func1: -> jqxhr = $.ajax("/api/my_list") .done (res) => @my_list = res.data return jqxhr
スマートに書けてコーディングが楽しくなりますね.
裏側で何をしているか,を意識しつつ,楽しいcoffeeライフをすごしましょう!