いいタイトルが思いつかなかったのですが、javascriptの method.call
の使い方がようやく分かったので備忘録。
tl;dr
//インフォメーション用のDomのテキストを変更するメソッド
function chMsg(text){...}
$('input.responsible')
.on('click', function(){ chMsg('click'); })
.on('change', function(){ chMsg('update!'); })
.on('input', function(){ chMsg('edit...'); });
みたいなことがしたいときに、chMsg
にthis
を渡すには、call
を使って以下のように書けば良さそうです。
function chMsg(text){
$(this).parent().find('.infomation').text(text);
}
$('input.responsible')
.on('click', function(){ chMsg.call(this, 'click'); })
.on('change', function(){ chMsg.call(this, 'update!'); })
.on('input', function(){ chMsg.call(this, 'edit...'); });
経緯
※面倒くさいので、ここからはCoffeeScriptで記述
「テキストフィールドの入力内容をチェックしたい。間違ってるとテキストフィールドが赤枠になるとか」
「やりましょう」
$(text_field).on 'change', ->
$self = $(this)
if myValidate $self.val()
$self.removeClass('no-good')
else
$self.addClass('no-good')
「これだと入力後じゃなくて入力と同時にチェックしてよ」
「たしかに」
$(text_field).on 'input', ->
$self = $(this)
if myValidate $self.val()
$self.removeClass('no-good')
else
$self.addClass('no-good')
「あー。入力後にチェック失敗したなら、何処かにメッセージ出すと親切だよね」
「ふむ」
$(text_field).on 'input', ->
$self = $(this)
if myValidate $self.val()
$self.removeClass('no-good')
else
$self.addClass('no-good')
.on 'change', ->
$self = $(this)
if myValidate $self.val()
$self.removeClass('no-good')
$self.parent.find('span').text('いいよ!')
else
$self.addClass('no-good')
$self.parent.find('span').text('おこだよ')
(クラスを付けたり消したりするところは、外に出したいな…)
inputValid ->
$self = $(this)
if myValidate $self.val()
$self.removeClass('no-good')
else
$self.addClass('no-good')
$(text_field)
.on 'input', inputValid
.on 'change', ->
inputValid
if myValidate this.value
$self.parent.find('span').text('いいよ!')
else
$self.parent.find('span').text('おこだよ')
と、ここまで来ると、changeイベントの時に外に出したinputValid
のthis
でテキストフィールドの値を取得できていないことによる不都合が置きます。
いくつかのjQueryプラグインを参考にすると、以下のように記述して、サブメソッドにイベントのthis
を渡しているようでした。
sub_method = ->
# thisを使った処理
$(targets).on 'change', ->
# thisを渡すためにeachを使う
$(this).each sub_method
このような書き方だとChrome / Firefoxでは動作するのですが、何故かIE8ではjQuery内部でエラーが起きて動かない…jQuery内部まで見て原因を調べるのも手間なので別の方法を探りました。
試しにjQuery.fn.eachについて調べてみると以下のようになっています。
(元のコードはjavascriptです)
# jQuery.fn.each
(callback, args)->
jQuery.each( this, callback, args )
# jQuery.each
(obj, callback, args) ->
value = undefined
i = 0
length = obj.length
isArray = isArraylike(obj)
# (※1) argsがある場合の処理..今回は関係ない
if args
if isArray
while i < length
value = callback.apply(obj[i], args)
break if value is false
i++
else
for i of obj
value = callback.apply(obj[i], args)
break if value is false
# A special, fast, case for the most common use of each
else
if isArray
while i < length
value = callback.call(obj[i], i, obj[i]) # ←これだ!
break if value is false
i++
else
for i of obj
value = callback.call(obj[i], i, obj[i])
break if value is false
# return
obj
なるほど。eachでは結局、call
をつかってコールバックを動かしているようですね。
というわけで、以下のように書いてみます
inputValid ->
$self = $(this)
if myValidate $self.val()
$self.removeClass('no-good')
else
$self.addClass('no-good')
$(text_field)
.on 'input', inputValid
.on 'change', ->
inputValid.call(this) # thisを渡して動かす
if myValidate this.value
$self.parent.find('span').text('いいよ!')
else
$self.parent.find('span').text('おこだよ')
ちゃんとIE8でも動きました。よく考えたら、eachと同じことをしているのに、なんで動いたのか謎です。ループなどの他の部分でひっかかっていたのかな…?
ところでjQuery.fn.eachに第二引数を渡せたんだ
上で引用しているjQuery.eachのコードだと、第二引数にargs
がある場合、jQuery.eachの(※1)の部分が呼ばれるようです。
$('li').each (a,b,c)->
console.log this #=> <li ...>, <li ...>, <li ...>,...
console.log a #=> 0,1,2,...
console.log b #=> <li ...>, <li ...>, <li ...>,...
console.log c #=> undefined , undefined , undefined ...
$('li').each (a,b,c)->
console.log this #=> <li ...>, <li ...>, <li ...>,...
console.log a #=> 'hello', 'hello', 'hello', ...
console.log b #=> 100,100,100,...
console.log c #=> Window, Window, Window,...
, ['hello', 100, this]
使いドコロが分かりません…
古いバージョンと互換性を保つために残っている使い方なのかとも思ったのですが、どうもjQueryの内部でjQuery.fn.loadというAjax関連のメソッドで第二引数を活用しているっぽいです。
jQueryのAPIドキュメントには第二引数を渡す使い方は書かれていないので、非推奨なのかな…?
なおjQueryの軽量クローンであるZeptoでは、第二引数をしたりはできなさそう。