フロントエンドイベントシステム(一)

10120 ワード

イベントはフロントエンドの中で、非常に重要な部分です.その役割は、ユーザの様々な行為に対応することである.近日はイベントシステムについてもっと深く勉強するつもりで、同時に、この部分の勉強の内容について総括します.ブラウザが現在に至るまで、イベントシステム自体が特に複雑になっているため、イベントという部分は多くの章に分けてまとめられる可能性があります.この章では、イベントシステムについて、個人の経験や他の場所で学んだことをまとめ、簡単な処理案を示しますが、後の章では古典的なjqueryのソースコードを導入して読みます.
バインド方式の整理
イベントシステムが発展してから現在に至るまで、イベントに対するバインド方法は3つあります.
  • は、要素ラベルに直接書きます.このような書き方は非常に古いものと言えるでしょうが、今でも使われている人がいます.イベントをバインドするためにこの方法を使用することは、現在では推奨されていません.その使用を推奨しない理由は、第2のバインド方法で説明されます.
  • 第1のonXXXの方法についても、
    <div onClick="function">
    をバインドする方法は、実際には第1のバインド方法と本質的に同じである.つまり私たちがよく言うdom 0事件です.前にも一種類の中で言いましたが、実は今はこの方法を推奨していません.理由は以下の通りです.
  • メソッドによってバインドされたイベントによって実行されるコールバック関数は、2つのバインドがバインドされている場合、2つ目は1つ目のバインドを上書きする.
  • この方式は、イベントバブル
  • のみをサポートする.
  • ieの下でのこの方式のコールバック関数は、私たちがいつものようにイベントオブジェクトパラメータを持つことはできません.
  • この方式はdom 3の一部の新規イベントに対してサポートされていない.また、FFの一部のプライベート実装についてもサポートされていない.

  • 実際には,この方式のバインドイベントについては,前の3点で決定され,この方法でバインドイベントを行うことはできない.4つ目はdom 3の一部の新規イベントのサポートではありませんが、実際には日常的な使用の中で、サポートされていないイベントに対しても、私たちの使用頻度は低いです.多くの新しい事件は、まだ私たちの視野に入っていないかもしれないので、廃棄されています.
  • 最後に私たちがよく言うdom 2イベントシステムですがdom 2イベントシステムは、現存する2つの異なるAPIを持っているので、以下では2つに分けます.ie側では、イベントのバインドについて、マイクロソフトがie 5に追加したAPI(イベントバインドのほか、対応する解縛、作成、配布など)である
    el.onclick = function
    の方法を採用している.彼は以前にonXXXメソッドを採用したことによる1つのコールバックのみを許可した場合を解決し、同種のイベントに対する複数のコールバックのバインドをサポートした.しかし、この案は、実はフロントエンドに何のメリットももたらしていません.一つのイベントシステムを処理するのに適していると、このような方法がもたらした無数の問題と、これらの問題の解決に多くの工夫を費やすことができるはずです.おおよその問題は以下の通りである
  • dom 3イベントに対するサポートされていない
  • thisの指向はバインドされた要素ではなく(個人的にはこれもthisが極めて特殊な場合を指しているような気がする)-複数のコールバックのバインドに対して、その実行順序は当然のようにバインドされた順序で実行されるのではなく、不規則な順序で実行される.
  • そのeventイベントオブジェクトとw 3 cのeventオブジェクトには大きな違いがある
  • .
  • も同様にイベントバブル
  • のみをサポートする.
    w 3 cについては、イベントのバインドについて、以下のシナリオ
    el.attachEvent("on"+type,callback)
    を採用しています.これは私たちの現代ブラウザで使用されている方法です.ie 9もこのAPIをサポートし始めました.これは私たちが現在最もよく使用しているシナリオであるはずです.もちろん、このメソッドにもいくつかの問題があります.
  • 前に述べたように、新しい事件自体は不安定です.まだ視界に入っていないかもしれませんが、廃棄されています.
  • 多くのブラウザはw 3 cの基準に従わず、いくつかのイベントに対して
  • をサポートしていない.
  • 気持ち悪いプレフィックス識別部分は、イベント名
  • に存在する.
  • は、w 3 cの規格が制定され、一部のブラウザメーカーよりも遅いため、初期のバージョンのブラウザでは、イベントのオブジェクトメンバーがw 3 cの規格と異なる場合が
  • である.

    イベントシステムの処理
    イベントシステムはフロントエンドの中で、極めて核心的な部分であるため、私たちはその様々な問題を一つ一つ処理しなければならない.強力なjqueryのイベント処理を捨てて、イベントシステムに対する処理を書き出して使用する場合は、少なくとも以下のいくつかの問題を解決する必要があります.
  • 異なるブラウザがイベントシステムのAPIに対してサポートする問題
  • IEのthis指向問題
  • イベントオブジェクトの相違問題
  • IEがコールバックを実行する順序の問題
  • では、問題を整理した以上、私たちは今からそれらの問題を解決することができます.
    異なるブラウザAPIのサポートに関する質問:
    われわれは条件判断を用いて簡単に実現すればよい
    el.addEventListener(type,callback,[phase])

    このように,問題1に対しては,解決といえるが,このようなイベント登録関数は,イベントシステムに対する簡易なニーズのページに対して十分である.しかし、それらの問題を提起した以上、一度に解決しましょう.
    ieの下でthisが指す問題について:
    問題外の話をすると、thisの指向について、実は簡単に言えば、誰が呼び出したのか、thisは誰を指しているのか.大まかにまとめると、日常のthisの指向は2つの場合、オブジェクトのthisは、対応するオブジェクトを指し、普通の関数のthisはwindowを指します.しかしattachEventのthisはwindowを指しているので、修正せざるを得ません.実現方式は簡単で,callやapplyを用いてthis指向を修正すればよい.したがって,上記のコードは以下のように修正できる.
    function addEvent(target,eventName,callback,useCapture){
    
        if(target.addEventListener){    //w3c    
            target.addEventListener(eventName,callback,useCapture);
        }else if(target.attachEvent){   //    ie   
            target.attachEvent("on"+eventName,callback);
        }else{      //       onXXX  
            target["on"+eventName] = callback;
        }
    
        //      ,        
        return callback;
    
    }
    
    function removeEvent(target,eventName,callback,useCapture) {
    
        if (target.removeEventListener) {
            target.removeEventListener(eventName, callback, useCapture);
        } else if (target.detachEvent) {
            target.detachEvent("on" + eventName, callback);
        } else {      //onXXX          null  
            target["on" + eventName] = null;
        }
    
    }

    イベント・オブジェクトの相違:
    eventオブジェクトおよびその処理もイベントバインドにおいて重要な内容である.具体的な処理は,これまでthisが処理中のhandlerに指し示すものについて一つ一つ解決してきた.
    大体target,currentTarget,バブル,デフォルトイベントをキャンセルして簡単に処理する.修正したコードは以下の通りです.
    function addEvent(target,eventName,callback,useCapture){
    
        if(target.addEventListener){    //w3c    
            target.addEventListener(eventName,handler,useCapture);
        }else if(target.attachEvent){   //    ie   
            target.attachEvent("on"+eventName,handler);
        }else{      //       onXXX  
            target["on"+eventName] = handler;
        }
    
        //  handler  ,     this      ,  ,  handler           
        function handler(){
            callback.call(target);
        }
    
        //      ,        
        return handler;
    
    }
    
    function removeEvent(target,eventName,callback,useCapture) {
    
        if (target.removeEventListener) {
            target.removeEventListener(eventName, callback, useCapture);
        } else if (target.detachEvent) {
            target.detachEvent("on" + eventName, callback);
        } else {      //onXXX          null  
            target["on" + eventName] = null;
        }
    
    }

    匿名関数のバインドに関する質問を追加します.
    以前はreturnコールバック関数の方法を採用していたが,同時に関数をバインドする際にコールバック関数を1つの変数で格納し,バインド解除時に変数を転送し,バインド解除の目的を達成した.
    次のコードを見てみましょう
    function addEvent(target,eventName,callback,useCapture){
    
        if(target.addEventListener){    //w3c    
            target.addEventListener(eventName,handler,useCapture);
        }else if(target.attachEvent){   //    ie   
            target.attachEvent("on"+eventName,handler);
        }else{      //       onXXX  
            target["on"+eventName] = handler;
        }
    
        //       ev
        function handler(event){
            //ie       window.event
            var ev = event || window.event,
                stopPropagation = ev.stopPropagation,
                preventDefault = ev.preventDefault;
    
            //          ie  ev.srcElement         ev.target
            ev.target = ev.target || ev.srcElement;
            //           (        )
            ev.currentTarget = ev.currentTarget || target;
            //       
            ev.stopPropagation = function(){
                if(stopPropagation){
                    stopPropagation.call(event);
                }else{
                    ev.cancelBubble = true;
                }
            };
            //         
            ev.preventDefault = function(){
                if(preventDefault){
                    preventDefault.call(event);
                }else{
                    ev.returnValue = false;
                }
            };
    
            //  callback  ,  this  ,   flag      
            var flag = callback.call(target,ev);
    
            //  flag        false   
            if(flag === false){
                ev.stopPropagation();
                ev.preventDefault();
            }
        }
    
        //      ,        
        return handler;
    
    }

    この方法は,実際には解縛にとってコード量が少ない.また,解縛時にコードを修正し,匿名関数を非匿名にして操作する必要もない.もちろん,匿名関数がネーミングを占有しないという欠点もあるが,このスキームは常に変数名を占有する必要がある.したがって,匿名関数の解縛に固執すれば,匿名関数を非匿名に変換することが考えられる.参照コードは次のとおりです.
    var a = addEvent(el,"click", function(){});
    removeEvent(el,"click",a);

    ieの実行順序の問題:
    多くの場合、イベントバインドの順序が要求されるに違いないため、順序に従って実行されないことは多くの場合見たくないので、コールバックの実行順序を処理する必要があります.処理の構想も複雑ではない.ieの下で、同じイベントに対して複数のコールバックを行った場合、私たちはそれを判断し、1つのコールバックに統合します.簡単に言えば、次のような考え方です.
    //    
    function addEvent(target,eventName,callback,useCapture){
    
        //       
        var fnStr = callback.toString().replace(/\s+/g,'');
    
        if(!target[eventName+"event"]){
            target[eventName+"event"] = {};
        }
    
        //        target[eventName+'event'][fnStr] 
        target[eventName+"event"][fnStr] = handler;
    
        useCapture = useCapture || false;
    
        //            
        if(target.addEventListener){
            target.addEventListener(eventName,handler,useCapture);
        }else if(target.attachEvent){
            target.attachEvent("on"+eventName,handler);
        }else{
            target["on"+eventName] = handler;
        }
    
        //       ev
        function handler(event){
            //ie       window.event
            var ev = event || window.event,
                    stopPropagation = ev.stopPropagation,
                    preventDefault = ev.preventDefault;
    
            //          ie  ev.srcElement         ev.target
            ev.target = ev.target || ev.srcElement;
            //           (        )
            ev.currentTarget = ev.currentTarget || target;
            //       
            ev.stopPropagation = function(){
                if(stopPropagation){
                    stopPropagation.call(event);
                }else{
                    ev.cancelBubble = true;
                }
            };
            //         
            ev.preventDefault = function(){
                if(preventDefault){
                    preventDefault.call(event);
                }else{
                    ev.returnValue = false;
                }
            };
    
            //  callback  ,  this  ,   flag      
            var flag = callback.call(target,ev);
    
            //  flag        false   
            if(flag === false){
                ev.stopPropagation();
                ev.preventDefault();
            }
        }
    }
    
    //    (    )
    function removeEvent(target,eventName,callback,useCapture){
        //    
        var fnStr = callback.toString().replace(/\s+/g,''),
                handler;
    
        if(!target[eventName+"event"]){
            return;
        }
    
        //        
        handler = target[eventName+"event"][fnStr];
        useCapture = useCapture || false;
    
        if(target.removeEventListener){
            target.removeEventListener(eventName,handler,useCapture);
        }else if(target.detachEvent){
            target.detachEvent("on"+eventName,handler);
        }else{
            target["on"+eventName] = null;
        }
    }

    上は考え方の抽象的なものですが、本当に具体的に実行し始めると、実は複雑です.
    簡単に言えば、この実行方式は、複数の関数をパッケージ化し、1つのイベントバインディングに捨てて実行することであり、addEventListenerまたはattachEventを用いて直接バインディングを行うと、どうしてもイベントバインディングのみの目的を達成することは難しい.(少なくともこの2つの関数を使って、直接要素をバインドして、私は何の良い解決策も思いませんでした.)もちろん、これも解決できないわけではありませんが、以前からDean Edwards大神のaddEventライブラリがこの問題を巧みに解決していました.彼は流行のaddeventListener/attachEvent法を採用していません.dom 0イベントシステムを直接採用して処理し、dom 0イベントシステムが1つのイベントしかバインドできない特性を巧みに利用しました.今流行っているjqueryイベントシステムは、多くの思想も参考にしているこのライブラリの思想です.
    従って、実行順序の処理については、上記のイベント登録においては言及されていない.しかし、後の章で言及します.また、このようなイベント登録は、複雑なページではなく、十分です.
    第1章もここまで・・・