とにかく、関数をラップするときに「this」が変更されるのを防ぐ方法はありますか?

StackOverflow https://stackoverflow.com/questions/400968

  •  03-07-2019
  •  | 
  •  

質問

通常のonclickイベントの前後にすべてのボタンにアクションを実行させたい。そこで、「華麗な」を思いつきました。これらすべての要素をループしてラッパー関数を作成するというアイデア。

これをテストするとかなりうまくいくように見えましたが、アプリに統合すると崩壊しました。ラッパーによって「this」の値が変更されたことがわかりました。サンプルコードはこれを示しています。イベントハンドラをラップする前に、クリックすると各ボタンにボタンIDが表示されますが、ラップした後、この例では表示名は「未定義」になります。フォーム内から実行する場合は「Form1」になります。

同じことをするより良い方法を知っている人はいますか?または、元々意図されていた「this」値を維持する良い方法ですか?

ご想像のとおり、ターゲットボタン内の既存のイベントハンドラーコードは変更しません。

事前に感謝します。

PS-ターゲットブラウザはIE6&です。アップ、クロスブラウザ機能は不要です

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<script language="javascript" type="text/javascript">
    function btnWrap_onClick()
    {
        var btns = document.getElementsByTagName("button");
        for( var i = 0; i < btns.length; i++)
        {
            var btn = btns[i];

            // handle wrap button differerntly
            if( "btnWrap" == btn.id)
            {
                btn.disabled = true;
                continue; // skip this button
            }

            // wrap it
            var originalEventHandler = btn.onclick;
            btn.onclick = function()
            {
                alert("Starting event handler");
                originalEventHandler();
                alert("Finished event handler");
            }
        }

        alert("Buttons wrapped successfully");
    }
</script>
<body>
    <p>
    <button id="TestButton1" onclick="alert(this.id);">TestButton1</button>
    <button id="TestButton2" onclick="alert(this.id);">TestButton2</button>
    </p>
    <button id="btnWrap" onclick="btnWrap_onClick();">Wrap Event Handlers</button>
</body>
</html>
役に立ちましたか?

解決

ポールディクソンのように、呼び出しが、代わりに適用

しかし、私が答えている理由は、不安なバグを見つけたからです。実際には、すべてのイベントハンドラを最後のボタンのイベントハンドラに置き換えています。あなたが意図した、それでしたか? (ヒント:各反復でoriginalEventHandlerの値を置き換えています)

以下のコードでは、機能するクロスブラウザソリューションが見つかります:

function btnWrap_onClick()
{
    var btns = document.getElementsByTagName("button");
    for( var i = 0; i < btns.length; i++)
    {
        var btn = btns[i];

        // handle wrap button differerntly
        if( "btnWrap" == btn.id)
        {
            btn.disabled = true;
            continue; // skip this button
        }

        // wrap it

        var newOnClick = function()
        {
            alert("Starting event handler");
            var src=arguments.callee;
            src.original.apply(src.source,arguments);
            alert("Finished event handler");
        }
        newOnClick.original = btn.onclick; // Save original onClick
        newOnClick.source = btn; // Save source for "this"
        btn.onclick = newOnClick; //Assign new handler
    }
alert("Buttons wrapped successfully");
}

まず、新しい匿名関数を作成し、変数 newOnClick に保存します。関数はオブジェクトなので、他のオブジェクトと同様に、関数オブジェクトにプロパティを作成できます。これを使用して、元のonclick-handlerであるプロパティ original と、 this になるソース要素である source を作成します。元のハンドラーが呼び出されます。

匿名関数の内部では、 original および source プロパティの値を取得できるように、関数への参照を取得する必要があります。匿名関数には名前がないため、を使用します。 arguments.callee (MSIE5.5以降でサポートされています)を使用して、その参照を取得し、変数srcに格納します。

次に、メソッド apply を使用して、元のonclickハンドラーを実行します。 apply は2つのパラメーターを取ります。最初のパラメーターは this の値になり、2番目のパラメーターは引数の配列になります。 this は、元のonclickハンドラーがアタッチされた要素でなければならず、その値は source に保存されました。 arguments は、すべての関数の内部プロパティであり、関数が呼び出されたすべての引数を保持します(匿名関数にはパラメーターが指定されていないことに注意してください。 arguments プロパティにあります。

apply を使用する理由は、匿名関数が呼び出されたすべての引数を転送できるためです。これにより、この関数が透明でクロスブラウザになります。 (Microsoftはイベントをwindow.eventに配置しますが、他のブラウザーはハンドラー呼び出しの最初のパラメーターでイベントを提供します)

他のヒント

call メソッドを使用して、バインディング、例えば originalEventHandler.call(btn);

代わりに、プロトタイプのようなライブラリが役立ちます-その bind メソッドを使用してビルドできます指定されたオブジェクトにバインドされた新しい関数。したがって、originalEventHandlerを var originalEventHandler = btn.onclick.bind(btn);

として宣言したことになります。

最後に、バインディングの問題に関する優れた背景知識については、 JavaScriptでバインド状態から抜け出す

あなたの問題は、JavaScriptでクロージャーが機能する方法です。正直なところ、フレームワークの使用をお勧めします。いずれの場合でも、イベント処理は手動で行うよりもはるかに優れている必要があります。

scroll top