jQuery 1.4.2以降、hoverイベントを設定した要素をclone(true)するとイベントが重複登録される件

前回同様、jQueryのバージョンアップで発生した不具合について。
jQuery 1.4.2 から現在最新の 1.6.2 に至るまで、hoverイベント(=mouseenter/mouseleaveイベント)を設定した要素を clone(true) でイベントごとコピーすると、マウスカーソルをあてたときに当該イベントが複数回発生するという事象が発生します。

背景

ある案件にて、動的に行の追加/削除を行える表をjQueryを使ったコードで実現しているのですが、jQueryを1.4.1からバージョンアップした際、追加行におけるマウスオーバー時の処理の挙動が変わってしまいました。具体的には、マウスオーバー時の処理が複数回呼び出される状態となりました。追加行は、末尾の行を clone(true) したものをベースとしています。
どのブラウザでも動作に違いはないようです。

再現コード

<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">google.load("jquery", "1.4.2");</script>
<script type="text/javascript">
    $(document).ready(function () {
        var plus = function (row, delta) {
            var countColumn = $(row).find('td.count');
            countColumn.text(parseInt(countColumn.text()) + delta);
        };

        // 1行目のhoverイベントを登録
        var firstRow = $('#table').find('tr:first');
        firstRow.hover(
            function() { plus(this, +1); },
            function() { plus(this, -1); }
        );

        // 1行目のクローンとして2行目を作成
        firstRow.clone(true).insertAfter(firstRow).find('td:first').text('clone');

        $('#version').text($().jquery);
    });
</script>

<table id="table">
    <tbody>
        <tr>
            <td>source</td>
            <td class="count">0</td>
        </tr>
    </tbody>
</table>
<div id="version"></div>

セルにマウスカーソルを乗せるとカウンタを増やし、マウスカーソルを外すとカウンタを減らすという単純なものです。

実行結果

1.4.2 以降、少なくとも現在の最新版である 1.6.2 まで、クローン側にマウスカーソルを乗せると 0 から 2 になり、外すと 2 から 0 になります。




回避方法

1. hover(mouseenter/mouseleave)の代わりにmouseover/mouseoutを使う

hover() は、mouseenterイベントとmouseleaveイベントを一発で設定するメソッドです。
今回の事象では、hover() 自体がおかしいわけではなく、mouseenterイベントとmouseleaveイベントがおかしなことになります。
似たようなイベントであるmouseoverイベントとmouseoutイベントで代用すれば、今回の事象を回避することができます。
ただし、イベントを付与する対象が子要素を持っている場合、挙動が変わる恐れがありますので注意が必要です。まあ、イベントとして別物ですし。

$('#table').find('tr:first')
    .mouseover(function() { plus(this, +1); })
    .mouseout(function() { plus(this, -1); });
2. delegate()を使う

そもそも、クローン作成対象の要素において、mouseenterイベントやmouseleaveイベントを直接バインドするのをやめることができないか検討してみましょう。
hover() / mouseenter() / mouseleave() などを使うのをやめて、delegate() でイベントを登録するようにすれば、今回の事象を回避することができます。

$('#table').delegate('tr', {
        mouseenter: function() { plus(this, +1); },
        mouseleave: function() { plus(this, -1); }
});

直接バインドするイベントがなくなれば、clone時に true を指定する必要すらなくなるわけですが、指定したままでも今回の事象は発生しません。

jQuery 1.4.3以降、表の列に対するセレクタを複数指定するとChromeで意図せぬ動作をする件

少し前に、ASP.NETな作業で使っていたjQueryのバージョンを、1.4.1 から 1.5.1 にアップデートしました。
すると、表の複数列を非表示にするのに使っていたコードが、Chromeで正しく動作しなくなってしまいました。具体的には、意図する列とは異なる列が非表示になってしまうという状態に。複数列を同時に選択するセレクタChromeでうまく動作しなくなってしまったようです。IEFirefoxでは問題ないのですが。
どうやら、1.4.2まではOKで、1.4.3以降からNGになってしまったよう。現時点の最新版である1.6.1でもNGです。

再現コード

<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">google.load("jquery", "1.6.1");</script>
<script type="text/javascript">
    $(document).ready(function() {
        // ★すべての行の、1列目と3列目を選択したい
        $('#table').find('td:nth-child(1), td:nth-child(3)').css('background-color', '#ffff00');
        
        $('#version').text($().jquery);
    });
</script>

<table id="table">
    <tbody>
        <tr>
            <td>one</td>
            <td>two</td>
            <td>three</td>
        </tr>
        <tr>
            <td>four</td>
            <td>five</td>
            <td>six</td>
        </tr>
        <tr>
            <td>seven</td>
            <td>eight</td>
            <td>nine</td>
        </tr>
    </tbody>
</table>
<div id="version"></div>

実行結果

IE(8)の表示



Firefox(3/4)の表示



Chrome(11/12)の表示


なお、以下のように列指定の順序を入れ替えたところ、また違った結果になりました。

$('#table').find('td:nth-child(3), td:nth-child(1)').css('background-color', '#ffff00');


回避方法

取り急ぎ、とりあえずセレクタをばらして、1列ずつ処理するようにしました。

$('#table').find('td:nth-child(1)').css('background-color', '#ffff00');
$('#table').find('td:nth-child(3)').css('background-color', '#ffff00');


もっとスマートな方法もあるのでしょうけど。。
あんまり情報探してないので、すでにjQuery開発元に報告が行ってるような事例なのかどうかは未確認。

てすと

SE兼PGです。現在入社6年目。
備忘録がてら、仕事とかの話などを残していく予定。