applyの利用してイベントのthisをbind(指定)させる方法(attachEventでthisを指定させる方法)

ちょっとはまったので、まとめとく。

きっかけ

元々の発端は、イベント関連の処理をしていて、FirefoxではaddEventListenerしたとき、
つまりobj.addEventListener(func, 'click', false);という処理を書くと
呼び出し先(func)のthisにobjが入ってくるわけだが、


IEでは、attachEvent先のthisがnullになるという仕様(?)になっている。*1

obj.attachEvent(func, 'onclick');

function func(){
 alert(this);  // null
 /* thisを使った処理をかく。
  * 例えばotherObj === this は常にnulなためfalseになる
  */
}

これだと、呼び出し先で、呼び出さし元のオブジェクトがわからないためDOM操作などができなくなり。困った困った。

解決策

結論から先に書くと、以下のように書けばthisを特定できて、呼び出し先のthisにobjがはいってくる。*2

function bindFunc(bind, func){
    return function(){
        return func.apply(bind, arguments);
    };
}

thisにobjを入れたい場合は、上記の式を書いて利用すればよい。
例えばこんな感じ

obj.addEventListener(bindFunc(obj, Func), click, false);  //Firefoxなど
obj.attachEvent(bindFunc(obj, Func), click);  // IE版

bindFuncを利用した場合は、thisにオブジェクト(この場合obj)がはいる。

前述の式を使うと

obj.attachEvent(bindFunc(this, func), 'onclick');

function func(){
 alert(this);  // objが入ってくる。
 /* thisを使った処理をかく。
  *  例えばotherObj === this は判定可能。
  */
}

attachEventにthisを指定させるだけならこれだけでOKです。

解説

じゃあ、ここでもっと詳しく理解するために、一つ一つひもといて行きましょう。

まず、applyとは...

「ほかのオブジェクトメソッドであるかのように別の関数を呼び出すこと」
第1引数には、呼び出す関数の元になるオブジェクトでthisキーワードになる
第2引数には、呼び出す関数に渡す引数の配列


2引数を伴う関数f()を、オブジェクトoのメソッドであるかのようにするコードは以下の通り。
f.apply(o,[1,2])


これは以下と同じ
o.m = f;
o.m(1,2);
delete o.m;

[http://www.oreilly.co.jp/books/9784873113296/:title=オライリー Javascript(第3版)*3 P128 7.5.5 apply()メソッドとcall()メソッドより]

簡単に言い換えるとapplyとは、

「関数fをoのオブジェクトメソッドであるかのように引数[1,2]を伴って呼び出す操作」

じゃあ、メソッドと関数の違いは?

メソッドとは...

オブジェクトを介して呼び出すJavaScriptの関数のことをメソッドと呼ぶ
メソッドを呼び出す時に使用したオブジェクトがメソッド本体の中でthisキーワードの値になる。

オライリー Javascript(第3版) P136 8.3 メソッド より

例えばo.m()で呼び出すと this == o である。


関数とは...

変数に格納された値であり、変数はグローバルオブジェクトのプロパティである。
言い換えると関数を呼び出すことはグローバルオブジェクトのメソッドを呼び出すことに等しい。
つまり、関数の中ではthisはグルオーバルオブジェクトを指す。

オライリー Javascript(第3版) P136 8.3 メソッド より

とどのつまり

結局の所、関数とメソッドにはそれほど大きな相違はない
しかし、メソッドを利用する目的はthisを特定することです。

オライリー Javascript(第3版) P136 8.3 メソッド より

ここで、bindFuncにもう一度登場してもらう

function bindFunc(bind, func){
    return function(){
        return func.apply(bind, arguments);
    };
}

このbindFunc関数をひもとくと...


bindFuncは関数をreturn値として返す関数(わけわかりませんねw)
じゃあ、どんな関数を返すの?(ここでは匿名関数を返す)。


func.apply(bind,arguments)を読み解くと...

  • bindはbindFunc関数の第1引数の値(具体的には何かのオブジェクト)
  • funcはbindFunc関数の第2引数の値(具体的には何かの関数)
  • argumentsはJavascriptの予約後で、呼び出された関数の引数を渡す(具体的にはbindFuncの引数)


まとめると、
「bindというオブジェクトで、funcという関数を、
bindのオブジェクトメソッドであるかのように呼び出して(thisを指定して)、引数にはargumentsを渡す。」

ということです。
さらに、言い換えると「bindを指定のthisで呼ばれるように func を束縛して関数化する。」

まあ、上記の説明でわからない人は2つの選択があります。

  1. bindFuncでは第1引数に、thisにしたいオブジェクトをいれて、第2引数に呼び出す関数入れると暗記して理解をあきらめる(w)
  2. すこし間違った抽象概念だけど、func.apply(obj, arguments)はobjのメソッドのようにfuncを呼び出す。と暗記

どっちも暗記かよw

まとめ

  • attachEventのthisはnullである。
  • これを解決するためにbindFuncというapplyを利用したものをつくる
  • bindFuncでthisをできるようになった。

※ 今回の例の場合、単にbindFuncだけを利用しても旨みはすくなくて、本当の利用場所は、new objectやprototyepなどをしたときにthisが指定できるのが大きいのです。

*1: はOK。(試してないけどね)

*2:objは前述のobj.addEventListenerのobj

*3:通常サイ本と呼ばれるもの