web-dev-qa-db-ja.com

jQueryイベントモデルと重複ハンドラーの防止

もう一度、$( "divid")。load(...)を使用して、独自のスクリプトを含むページをdivにロードします。私が直面する問題はイベントに関連しています。親ページからtrigger( "monkey")を呼び出し、読み込まれたページでbind( "monkey")を実行して、alert( "monkey bound")を実行するとします。同じloadメソッドが複数回呼び出された場合、バインドは複数回呼び出されます。これで、バインドする前にバインドを解除するか、バインド前にハンドラーの数を確認してからバインドしないで、これを防ぐことができます。どちらのオプションも、後で別の「サブページ」(divにロードされたページ)でそのトリガーにバインドする場合のようにスケーラブルではありません。

私が理想的には、追加しようとしているハンドラーがすでに存在するかどうかを確認しますが、匿名ハンドラーを使用したいのですが...(最後のリクエストについて少し尋ねます)。現在、私は事前定義/名前付きメソッドを使用して、バインドの前にこれをチェックすることで回避策を持っています。

// Found this on StackOverflow
function getFunctionName(fn)
{
 var rgx = /^function\s+([^\(\s]+)/
 var matches = rgx.exec(fn.toString());
 return matches ? matches[1] : "(anonymous)"
}

function HandlerExists(triggerName, handlerName) {
        exists = false;
        if ($(document).data('events') !== undefined) {
            var event = $(document).data('events')[triggerName];
            if(event !== undefined)
            {
                $.each(event, function(i, handler) {
                    alert(handlerName);
                    if (getFunctionName(handler) == handlerName) {
                        exists = true;
                    }
                });
            }
        }
        return exists;
    }

これは、私が感じているかなり粗雑な方法ですが、機能しているようです。次のように、バインドの前に以下を実行します。

if (!HandlerExists("test", "theMethod")) {
    $(document).bind("test", theMethod);
}

誰かがよりエレガントな解決策を持っていますか?たとえば、特定のスクリプトがロードされていることを確認する方法はありますか?そのため、最初のロードでgetScript()を使用してjsを子ページからロードし、その後のロードではjsをロードしません(そして既存のjsによって処理されるトリガーを起動するだけです)。

34
Mr AH

jQueryのイベント名前空間を使用して重複バインディングを防止する

重複を防ぐ方法はいくつかあります。 1つはバインド解除で元のハンドラーを渡すだけですが、それがコピーであり、メモリ内の同じスペースにない場合はバインド解除されません。他の一般的な方法(名前空間を使用)は、これを実現するより確実な方法です。

これはイベントの一般的な問題です。そこで、jQueryイベントについて少し説明し、名前空間を使用してバインディングが重複しないようにします。



ANSWER:(要点まで短く、まっすぐ)


// bind handler normally
$('#myElement').bind('myEvent', myMainHandler);

// bind another using namespace
$('#myElement').bind('myEvent.myNamespace', myUniqueHandler);

// unbind the unique and rebind the unique
$('#myElement').unbind('myEvent.myNamespace').bind('myEvent.myNamespace', myUniqueHandler);
$('#myElement').bind('myEvent.myNamespace', myUniqueHandler);

// trigger event
$('#myElement').trigger('myEvent');

// output
myMainHandler() // fires once!
myUniqueHandler() // fires once!



回答の例:(完全な詳細説明)


まず、バインドするサンプル要素を作成しましょう。 #buttonのIDを持つボタンを使用します。次に、イベントにバインドするためのハンドラとして使用できる3つの関数を作成します。

function exampleOne()クリックでバインドします。 function exampleTwo()クリックの名前空間にバインドします。 function exampleThree()クリックの名前空間にバインドしますが、他のバインドを削除せずにバインドを解除して複数回バインドするため、バインドされたメソッドを削除せずにバインドの重複を防ぎます。

開始例:(バインドする要素とハンドラーになるいくつかのメソッドを作成します)

<button id="button">click me!</button>


// create the example methods for our handlers
function exampleOne(){ alert('One fired!'); }
function exampleTwo(){ alert('Two fired!'); }
function exampleThree(){ alert('Three fired!'); }

バインドするexampleOneをクリック:

$('#button').bind('click', exampleOne); // bind example one to "click" 

ここで、ユーザーがボタンをクリックするか$( '#button')。trigger( 'click')を呼び出すと、「One Fired!」というアラートが表示されます。

exampleTwoをclickの名前空間にバインドします: "名前は任意です。myNamespace2を使用します"

$('#button').bind('click.myNamespace2', exampleTwo);

これのクールな点は、exampleOne()とexampleTwo()を起動する「クリック」をトリガーできること、またはexampleTwo()のみを起動する「click.myNamespace2」をトリガーできることです。

exampleThreeをclick:の名前空間にバインドします。「ここでも、exampleTwoの名前空間と異なる限り、名前は任意です。myNamespace3を使用します。」

$('#button').bind('click.myNamespace3', exampleThree);

「クリック」がトリガーされると、3つのサンプルメソッドがすべて起動されるか、特定の名前空間をターゲットにすることができます。

重複を防ぐためにすべてをまとめてください

ExampleThree()を次のようにバインドし続ける場合:

$('#button').bind('click.myNamespace3', exampleThree); 
$('#button').bind('click.myNamespace3', exampleThree);
$('#button').bind('click.myNamespace3', exampleThree);

Bindを呼び出すたびにイベント配列に追加するため、これらは3回発生します。とても簡単です。次のように、バインドする前にその名前空間のバインドを解除するだけです。

$('#button').unbind('click.myNamespace3').bind('click.myNamespace3', exampleThree); 
$('#button').bind('click.myNamespace3', exampleThree);
$('#button').unbind('click.myNamespace3').bind('click.myNamespace3', exampleThree); 
$('#button').bind('click.myNamespace3', exampleThree);
$('#button').unbind('click.myNamespace3').bind('click.myNamespace3', exampleThree); 
$('#button').bind('click.myNamespace3', exampleThree);

クリック関数がトリガーされると、exampleOne()、exampleTwo()、exampleThree()は一度だけ発生します。

単純な関数ですべてをまとめるには:

var myClickBinding = function(jqEle, handler, namespace){
    if(namespace == undefined){
        jqEle.bind('click', handler);
    }else{
        jqEle.unbind('click.'+namespace).bind('click.'+namespace, handler);
    }
}   

まとめ:

jQueryイベント名前空間はメインイベントへのバインドを可能にしますが、兄弟の名前空間や親の名前空間に影響を与えることなく子の名前空間を作成およびクリアすることもできます。

詳細な説明: http://api.jquery.com/event.namespace/

53
eli geske

からの素晴らしい答え

イベントを1回だけバインド

copyPastedInfo

それを適用できる場合は、おそらく event.preventDefaultt および event.stopPropagation ORバインドを解除して毎回バインドする)を確認してください。のようなメソッド内

function someMethod()
{
  $(obj).off('click').on('click', function {});
}
26
user1028880

うーんone()を使うのはどうですか? http://api.jquery.com/one/

それとも私はあなたを完全に誤解していますか?

7
prodigitalson

これは完全な答えではありませんが、最小限の変更ですべてのコードを修正する方法をすぐに見つけました。私の「ベース」jsクラス(これは、すべてのページでクラス名などによる標準ボタンのフックアップに使用されるオブジェクトです)で、次のメソッドを追加して、重複ハンドラーをチェックします。

BindOnce: function(triggerName, fn) {
    function handlerExists(triggerName, theHandler) {
        function getFunctionName(fn) {
            var rgx = /^function\s+([^\(\s]+)/
            var matches = rgx.exec(fn.toString());
            return matches ? matches[1] : "(anonymous)"
        }
        exists = false;
        var handlerName = getFunctionName(theHandler);
        if ($(document).data('events') !== undefined) {
            var event = $(document).data('events')[triggerName];
            if (event !== undefined) {
                $.each(event, function(i, handler) {
                    if (getFunctionName(handler) == handlerName) {
                        exists = true;
                    }
                });
            }
        }
        return exists;
    }
    if (!handlerExists(triggerName, fn)) {
        $(document).bind(triggerName, fn);
    }
},

次に、bindメソッドの代わりにそれを呼び出します!

$.mystuff.BindOnce("TheTrigger", OnTriggered)

1、2、3などと呼ばれるだけで、ここでanonメソッドを使用することはできません。重複をチェックする唯一の方法は、メソッドのtoString()を使用することです。これは、複雑なアプリケーションではかなり遅くなります。

6
Mr AH

約100年も遅すぎますが、無名関数を使用していない場合は、最初にunbindを使用することでこれをはるかに簡単に行うことができます。

$.fn.eventWillOnlySubscribeOnce = function () {
    return this.each(function () {
        var oneHandler = (function () {
             HANDLER CODE
        });

    $(this).unbind("submit", oneHandler);
    $(this).bind("submit", oneHandler);
});
};

この実装はFirefox 4では機能しますが、他の多くのブラウザーでは機能しません。ハンドラー変数が毎回新しく作成されるため、バインド解除するために一致するハンドラーをバインド解除で見つけることができないためです。ハンドラーを親スコープに移動すると、問題が解決します。

var oneHandler = (function () {
    HANDLER CODE
});

$.fn.eventWillOnlySubscribeOnce = function () {
    return this.each(function () {
    $(this).unbind("submit", oneHandler);
    $(this).bind("submit", oneHandler);
});
};

おそらくFirefoxは巧妙な最適化を行っていると思われます。つまり、ハンドラーは変更されないため、どこかで定数変数として保持されます。

1
Massif