1

javascript で動的に構築されたイベント ハンドラーの管理に苦労しています。

いくつかの場所で、特定のイベント (主にマウスオーバー、マウスアウト、クリック) を処理する必要があるフォームまたはコントロールを作成しています。

秘訣は、多くの場合、イベント ハンドラー自体が、フォームまたはコントロールを構築する関数によって生成されるか、関数に渡されるデータを組み込む必要があることです。

そのため、「eval()」を使用してイベントを構築し、適切なデータを組み込んでいますが、これはある程度うまく機能しています。

問題は、「eval() を使うべきではない!」のようなものを見たり聞いたりし続けることです。動的に構築されたイベント ハンドラーが他のイベント ハンドラーを動的に構築する必要があり、ネストされた eval がかなり鈍い (穏やかに言えば) いくつかのますます醜い実装と同様に。

だから私はここにいて、誰かが私にもっと良い方法を教えてくれるかどうか尋ねています (ネイティブの JavaScript のみでお願いします。私はサードパーティのライブラリを実装していません!)。

私が話していることを説明するための大まかな例を次に示します。

function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked)
{
    var inp = document.createElement('input');
    inp.id = controlName;
    inp.type = type;
    inp.style.cssText = dormantStyle;
    eval("inp.onfocus = function() { this.style.cssText = '" + activeStyle + "'; }");
    eval("inp.onblur = function() { this.style.cssText = '" + dormantStyle + "'; }");
    eval("inp.onclick = function() { " + whenClicked + "; }");
    return inp;
}

この関数を使用すると、多数の異なる INPUT タグを簡単に作成し、多数の一意の属性とイベント アクションを指定できます。それぞれに対して関数を 1 回呼び出すだけです。繰り返しますが、これは非常に単純化された例です。私が話していることを示すためだけに、私が現在取り組んでいるプロジェクトでは、場合によっては、イベントに数十行を組み込むことができ、渡されたパラメータまたはその他の動的に生成されたデータ。より極端なケースでは、ハンドラーまたはハンドラーのハンドラーの動的に生成されたコンテンツに基づいて、個々の行/列/セルがイベントを処理する必要があるテーブルを作成します。

最初は、上記のような関数を次のように作成しました。

function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked)
{
    var inp = document.createElement('input');
    inp.id = controlName;
    inp.type = type;
    inp.style.cssText = dormantStyle;
    inp.onfocus = function() { this.style.cssText = activeStyle; };
    inp.onblur = function() { this.style.cssText = dormantStyle; };
    eval("inp.onclick = function() { " + whenClicked + "; }");
    return inp;
}

...しかし、「activeStyle」に最後に割り当てられた値が何であれ、「domantStyle」が、作成されたすべてのハンドラーで使用される値になることがわかりました (たとえば、それぞれが独自のスタイルのセットを保持するのではなく)。それが、関数が作成されたときに eval() を使用して変数の値を「ロックイン」するようになった理由ですが、これにより、次のような悪夢に陥りました。

(これは、私が現在取り組んでおり、ネストされた eval() 関数を使用する動的に構築されたイベント ハンドラーのサンプルです):

    eval("input.onkeyup = function() { " +
            "InputParse(this,'ucwords'); " +
            "var tId = '" + myName + This.nodeName + "SearchTable" + uidNo + "'; " +
            "var table = document.getElementById(tId); " +
            "if (this.value.length>2) { " +
                "var val = (this.value.indexOf(',') >=0 ) ? this.value.substr(0,this.value.indexOf(',')) : this.value; " +
                "var search = Global.LoadData('?fn=citySearch&limit=3&value=' + encodeURI(val)); " +
                "if (table) { " +
                    "while (table.rows.length>0) { table.deleteRow(0); } " +
                    "table.style.display='block'; " +
                "} else { " +
                    "table = document.createElement('table'); " +
                    "table.id = tId; " +
                    "ApplyStyleString('" + baseStyle + ";position=absolute;top=20px;left=0px;display=block;border=1px solid black;backgroundColor=rgba(224,224,224,0.90);zIndex=1000;',table); " +
                    "var div = document.getElementById('" + divName + "'); " +
                    "if (div) { div.appendChild(table); } " +
                "} " +
                "if (search.rowCount()>0) { " +
                    "for (var i=0; i<search.rowCount(); i++) { " +
                        "var tr = document.createElement('tr'); " +
                        "tr.id = 'SearchRow' + i + '" + uidNo + "'; " +
                        "tr.onmouseover = function() { ApplyStyleString('cursor=pointer;color=yellow;backgroundColor=rgba(40,40,40,0.90);',this); }; " +
                        "tr.onmouseout = function() { ApplyStyleString('cursor=default;color=black;backgroundColor=rgba(224,224,224,0.90);',this); }; " +
                        "eval(\"tr.onclick = function() { " +
                            "function set(id,value) { " +
                                "var o = document.getElementById(id); " +
                                "if (o && o.value) { o.value = value; } else { alert('Could not find ' + id); } " +
                            "} " +
                            "set('" + myName + This.nodeName + "CityId" + uidNo + "','\" + search.id(i)+ \"'); " +
                            "set('" + myName + This.nodeName + "ProvId" + uidNo + "','\" + search.provId(i)+ \"'); " +
                            "set('" + myName + This.nodeName + "CountryId" + uidNo + "','\" + search.countryId(i) + \"'); " +
                            "set('" + input.id + "','\" + search.name(i)+ \"'); " +
                            "}\"); " +
                        "var td = document.createElement('td'); " +
                        "var re = new RegExp('('+val+')', 'gi'); " +
                        "td.innerHTML = search.name(i).replace(re,'<span style=\"font-weight:bold;\">$1</span>') + ', ' + search.provinceName(i) + ', ' + search.countryName(i); " +
                        "tr.appendChild(td); " +
                        "table.appendChild(tr); " +
                    "} " +
                "} else { " +
                    "var tr = document.createElement('tr'); " +
                    "var td = document.createElement('td'); " +
                    "td.innerHTML = 'No matches found...';" +
                    "tr.appendChild(td); " +
                    "table.appendChild(tr); " +
                "} " +
            "} else { " +
                "if (table) table.style.display = 'none'; " +
            "} " +
        "} ");

現在、「.onclick」イベントをテーブル行にバインドするためにネストされた eval() を取得する際に問題が発生しています。理由)...だから、「eval()」ステートメントの恐ろしい使用を避けながら、これらの同じ目標を達成できる方向に誰かが私を向けることができれば、本当に感謝しています!

ありがとう!

4

2 に答える 2

1

これが、他の多くの理由の中でも特に、 を使用してはならないeval理由です。(「焼き付け」ている値に引用符が含まれている場合はどうなりますか? おっと。) そして、より一般的には、間違った方法で送信するのではなく、正しい方法が機能しない理由を理解しようとします。:)

またon*、属性に割り当てることはお勧めできません。それらは特にうまくスケーリングしません。新しい話題はelement.addEventListener、同じイベントに対して複数のハンドラーを許可する を使用することです。(古い IE の場合attachEventは、.


クロージャーを使用する貼り付けたコードは、問題なく動作するはずです。含めなかった部分は、これをループで実行していたに違いないということです。

JavaScript 変数は、ブロック スコープではなく関数スコープであるため、これを行うと、次のようになります。

var callbacks = [];
for (var i = 0; i < 10; i++) {
    callbacks.push(function() { alert(i) });
}

for (var index in callbacks) {
    callbacks[index]();
}

... 910回出ます。ループを実行するたびに、同じvariableを閉じる関数が作成iされ、次の反復で の値がi変化します。

必要なのは、インラインまたは独立したファクトリ関数です。

for (var i = 0; i < 10; i++) {
    (function(i) {
        callbacks.push(function() { alert(i) });
    })(i);
}

これにより、別の関数が作成され、すぐに実行されます。関数のi 内側は毎回異なる変数です (関数にスコープが設定されているため)。したがって、これは効果的に外側 iの値をキャプチャし、それ以上の変更を無視します。

これを明示的に分割できます。

function make_function(i) {
    return function() { alert(i) };
}

// ...

for (var i = 0; i < 10; i++) {
    callbacks.push(make_function(i));
}

まったく同じことですが、関数はインラインではなく独立して定義されています。

これは以前にも出てきましたが、驚きの原因を特定するのは少し難しいです。


「正しい方法」のコードでさえ、関数やスタイルのコンテンツに文字列を使用しています。そのクリック動作を関数として渡し、JavaScript に CSS のチャンクを埋め込む代わりにクラスを使用します。(すべての入力に ID を追加するかどうかも疑問です。)

だから私はこのようなものを書きます:

function create_input(id, type, active_class, onclick) {
    var inp = document.createElement('input');
    inp.id = id;
    inp.type = type;
    inp.addEventListener('focus', function() {
        this.className = active_class;
    });
    inp.addEventListener('blur', function() {
        this.className = '';
    });
    inp.addEventListener('click', onclick);

    return inp;
}

// Called as:
var textbox = create_input('unique-id', 'text', 'focused', function() { alert("hi!") });

これにはまだいくつかの問題があります。古い IE では機能せず、後で追加しようとしたクラス名がすべて削除されます。jQuery が人気がある理由は次のとおりです。

function create_input(id, type, active_class, onclick) {
    var inp = $('<input>', { id: id, type: type });
    inp.on('focus', function() {
        $(this).addClass(active_class);
    });
    inp.on('blur', function() {
        $(this).removeClass(active_class);
    });

    inp.on('click', onclick);

    return inp;
}

もちろん、これらのほとんどは不要です:focus。CSS セレクターを使用するだけで、 focusandblurイベントをまったく気にする必要はありません。

于 2013-02-11T07:18:38.363 に答える
0

eval値を「固定」する必要はありません。

CreateInput投稿されたコードからは、リターン後に値が変化する理由は明らかではありません。ループを実装した場合、最後に割り当てられた値が使用されるとCreateInput予想されます。しかし、ループから呼び出しても、コメンターとは対照的に、あなたが説明する誤動作は発生しません。activeStyledormantStyleCreateInput

とにかく、この種の古いデータに対する解決策は、クロージャを使用することです。JavaScript ローカル変数は、関数の奥深くで宣言されているかループで宣言されているかに関係なく、すべて関数呼び出しのスコープにバインドされます。したがって、関数呼び出しを追加して、新しい変数を強制的に作成します。

function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked)
{
    while ( something ) {
        activeStyle += "blah"; // modify local vars
        function ( activeStyle, dormantStyle ) { // make copies of local vars
            var inp = document.createElement('input');
            inp.id = controlName;
            inp.type = type;
            inp.style.cssText = dormantStyle;
            inp.onfocus = function() { this.style.cssText = activeStyle; };
            inp.onblur = function() { this.style.cssText = dormantStyle; };
            inp.onclick = whenClicked;
        }( activeStyle, dormantStyle ); // specify values for copies
    }
    return inp;
}
于 2013-02-11T07:16:47.690 に答える