JavaScriptで、thisに変数を指定したのはいいけれど、関数からアクセスできなかったり、予期せぬ値が返ってきたりでうわーてなることを何回か繰り返すうちに、それらの挙動が結合(binding)という挙動に関わっていることをやっと理解できてきました。というわけで、結合についての挙動をメモします。
結合の挙動
『Effective JavaScript JavaScriptを使うときに知っておきたい68の冴えたやり方』という参考書によると、結合に関して次のようなことが書かれています。
どの関数にもthisが暗黙のうちに結合され、その値は関数が呼び出されたときに決まる。
thisは、それを囲む最も近い関数(つまり、そのすぐ外側の関数)によって暗黙のうちに結合される。
例えば、hogeオブジェクトにgetInfo関数を作り、それをpiyoから呼び出すことを考えます。
var hoge = { info : "Hoge", getInfo : function() { return this.info } }; document.writeln(hoge.getInfo()); // Hoge var piyo = { info : "Piyo", getInfo : hoge.getInfo // ① }; document.writeln(piyo.getInfo()); // Piyo
①でpiyoのgetInfoをhogeのgetInfoに置き換えている。なので、piyo.getInfoを呼び出せば、hoge.getInfoが呼び出されて"Hoge"が表示されそうですが、結果は"Piyo"が表示されます。
「thisの値は関数が呼び出された時に結合される」という規則に従うと、piyo.getInfoが呼び出されたとき、getInfo内のthisに結合されるinfoがどこにあるかをpiyoの中で探し、見つけたinfoを結合する。なので、piyoで定義したinfoが表示される。また、piyoのinfoをコメントアウトするとundefinedになる。
this指定のある関数を引き渡すときの注意
さきほどのhogeオブジェクトはそのままにして、hoge.getInfoで取得した値をpiyo.infoにセットする下記のようなコードに書き換えてみます。
var piyo = { info : "Piyo", getInfo : function() { return this.info }, setInfo : function(func) { this.info = func(); } }; piyo.setInfo(hoge.getInfo); document.writeln(piyo.getInfo()); // undefined
前の結果を参考にすると、この挙動がなんとなく見えてくる。setInfoの引数funcに指定されたhoge.getInfoのthisに結合されるinfoをpiyo.setInfo内で探すけれど、そのinfoは無いのでpiyo.getInfoの結果はundefinedとなる。setInfo内にinfo="Moge";とか書くと、piyo.getInfoは"Moge"になる。
解決策としては、必ず"Hoge"を返すgetInfoを呼び出すローカル関数を定義して、それを引数にしてsetInfoを呼び出す。
piyo.setInfo(function(){ return hoge.getInfo(); }); document.writeln(piyo.getInfo()); // Hoge
または、bind()という関数オブジェクトのメソッドを利用する。bindは、対象の関数を引数で指定したオブジェクトのメソッドして呼び出すことができるようなメソッド。
piyo.setInfo(hoge.getInfo.bind(hoge));
document.writeln(piyo.getInfo()); // Hoge
うーん、なんとなく結合の挙動が見えてきた感じですね。