Javascriptフレームワークのカスタムイベント

7772 ワード

多くのjavascriptフレームワークでは、jquery、yui、dojoなどのカスタムイベント(custom events)が「document ready」イベントをサポートしています.一部のカスタムイベントはコールバック(callback)に由来します.
コールバックは複数のイベントハンドルを配列に格納し、トリガ条件を満たすと、コールバックシステムは配列から対応するハンドルを取得して実行します.では、これはどんな落とし穴があるのでしょうか.この質問に答える前に、コードを見てみましょう.
次の2つのコードをdomcontentloadedイベントに順次バインドします.
document.addeventlistener("domcontentloaded", function() {
  console.log("init: 1");
  does_not_exist++; //        
}, false);

document.addeventlistener("domcontentloaded", function() {
  console.log("init: 2");
}, false);

では、このコードを実行すると、どのような情報が返されますか?明らかに、これら(または類似)が見えます.
init: 1
error: does_not_exist is not defined
init: 2

2つのセグメント関数が実行されていることがわかります.最初の関数が例外を投げ出したとしても、2番目のセグメントコードの実行には影響しません.
面倒
OK、よくあるフレームワークのコールバックシステムを見てみましょう.まず、jqueryの(流行しているから):
$(document).ready(function() {
  console.log("init: 1");
  does_not_exist++; //        
});

$(document).ready(function() {
  console.log("init: 2");
});

コンソールに何が出力されますか?
init: 1
error: does_not_exist is not defined

これで問題は明らかになった.コールバックシステムは実際には脆弱です.中間のセグメントコードが異常を投げ出した場合、残りは実行されません.実際の状況では、この結果はさらに深刻になる可能性があります.例えば、悪いプラグインが「古い糞がおかゆを壊した」可能性があります.
他のフレームワークでは、dojoの場合はjqueryと似ていますが、yuiの場合は少し違います.そのコールバックシステムではtry/catch文を使用して異常による中断を回避します.しかし、小さなマイナスの影響がありますが、それに対応する異常は見えません.
yahoo.util.event.ondomready(function() {
  console.log("init: 1");
  does_not_exist++; //        
});

yahoo.util.event.ondomready(function() {
  console.log("init: 2");
});

出力:
init: 1
init: 2

では、完璧な解決策はありますか?
ソリューション
コールバックとイベントを結びつける解決策を考えました.コールバックがトリガーされたときに実行されるイベントを作成できます.各イベントには独立した実行環境(execution context)があるため、イベントが例外を投げ出しても他のコールバックには影響しません.
これは少し複雑に聞こえますが、コードで話しましょう.
var currenthandler;

//       
if (document.addeventlistener) {
    document.addeventlistener("fakeevents", function() {
        //     
        currenthandler();
    }, false);

    //     
    var dispatchfakeevent = function() {
        var fakeevent = document.createevent("uievents");
        fakeevent.initevent("fakeevents", false, false);
        document.dispatchevent(fakeevent);
    };
} else {
    //    ie           
}

var onloadhandlers = [];

//         
function addonload(handler) {
    onloadhandlers.push(handler);
};

//       ,            
onload = function() {
    for (var i = 0; i < onloadhandlers.length; i++) {
        currenthandler = onloadhandlers[i];
        dispatchfakeevent();
    }
};

準備が整いました.上のコードを新しいコールバックシステムに投げましょう.
addonload(function() {
  console.log("init: 1");
  does_not_exist++; //        
});

addonload(function() {
  console.log("init: 2");
});

神のご加護を見て、運行結果を見て、私たちは以下の情報を見ました.
init: 1
error: does_not_exist is not defined
init: 2

いいですね.これが私たちが望んでいることです.この2つのコールバックはいずれも動作し、互いに影響を及ぼさず、異常な情報を得ることができてよかったです.
では、インターネットexplorerという「阿斗」(観客のアドバイスを聞いた)を振り返る.インターネットexplorerはw 3 cの標準イベント仕様をサポートしていません.幸いなことに、fireeventsの方法がありますが、ユーザーイベントのときにしかトリガーできません(例えば、clickをクリックするなど).
しかし、やっとドアが見つかりました.具体的なコードを見てみましょう.
var currenthandler;

if (document.addeventlistener) {
    //        
} else if (document.attachevent) { // msie
    //       ,          
    document.documentelement.fakeevents = 0;
    document.documentelement.attachevent("onpropertychange", function(event) {
        if (event.propertyname == "fakeevents") {
            //     
            currenthandler();
        }
    });

    dispatchfakeevent = function(handler) {
        //    propertychange   
        document.documentelement.fakeevents++;
    };
}

簡単に言えば、インターネットexplorerに対してpropertychangeイベントをトリガとして使用しただけです.
更新
一部のユーザーはsettimeoutを使用することを推奨しています.
try { callback(); } catch(e){ settimeout(function(){ throw e; }, 0); }

次は私の考えです
       ,              。
                  。

    ,                  
  ,                    。
                 ,     
 ,         、     (    )、
      --             。

要するに,インターネットexplorerを含めてイベントを用いてコールバックを実行する実装が最も重要である.イベントエージェントベースのコールバックシステムを作成している場合は、この技術に興味があると思います.
更新2
prototypeはインターネットexplorerに対するカスタムイベント処理においても、上述の方法とコールバックをトリガーする.
http://andrewdupont.net/2009/03/24/link-dean-edwards/
注、prototype 1.6対応のコードは、以下の通りです.
function createwrapper(element, eventname, handler) {
    var id = geteventid(element); //         id
    var c = getwrappersforeventname(id, eventname); //             
    if (c.pluck("handler").include(handler)) return false; //       

    //     
    var wrapper = function(event) {
        if (!event || !event.extend ||
                (event.eventname && event.eventname != eventname))
            return false;

        event.extend(event);
        handler.call(element, event);
    };

    //        
    wrapper.handler = handler;
    c.push(wrapper);
    return wrapper;
}

function observe(element, eventname, handler) {
    element = $(element);                  //        
    var name = getdomeventname(eventname); //       

    var wrapper = createwrapper(element, eventname, handler); //     

    if (!wrapper) return element;

    //     
    if (element.addeventlistener) {
        element.addeventlistener(name, wrapper, false);
    } else {
        element.attachevent("on" + name, wrapper);
    }

    return element;
}

//     
document.observe("dom:loaded", function() {
    console.log("init: 1");
    does_not_exist++;
});

document.observe("dom:loaded", function() {
    console.log("init: 2");
});

prototypeの作者を楽しませてくれた:-/
-- split --
本人から見れば、原文の著者が述べた技術点は、丈夫なコールバックシステムをどのように作成するかのほかに、実は2つある.
1つは、異常が発生した場合、所望のコードを実行し続けることをどのように保証するかである.2つ目は、互いに干渉しない「実行環境」を作成する方法です.
原文で述べたcreateeventとsettimeoutはいずれも良い方法であり、原作者が言ったコールバックシステムを処理するだけで、確かにcreateeventを使用するのが適切である.settimeoutに対応する詳細は、realazy兄の関連記事に移動できます.
エラーが発生しても所望のコードを実行し続けることができますが、finally文を使用することも考えられます.次の例を示します.
var callbacks = [
  function() { console.log(0); },
  function() { console.log(1); throw new error; },
  function() { console.log(2); },
  function() { console.log(3); }
];

for(var i = 0, len = callbacks.length; i < len; i++) {
    try {
        callbacks[i]();
    } catch(e) {
        console.info(e); //       
    } finally {
        continue;
    }
}

このインスピレーションは同じようにdean edwardsの文章からの返事で、ここにも貼っておきましょう.
function iterate(callbacks, length, i) {
    if (i >= length) return;

    try {
        callbacks[i]();
    } catch(e) {
        throw e;
    } finally {
        iterate(callbacks, length, i+1);
    }
}

最後に、ちょっとした問題を残します.上記のコードの中で、伝言者が提出したなぜ異常が最後まで印刷されたのか誰が知っていますか?