JavaScript イベント メカニズムの互換性ソリューションを詳細に説明するコード グラフィックスとテキスト

黄舟
リリース: 2017-03-30 16:34:56
オリジナル
1118 人が閲覧しました

この記事のソリューションは、以下を通じて JavaScript ネイティブオブジェクト およびホスト オブジェクト (dom 要素) に使用できます。メソッド events をバインドしてトリガーするには:

または

var input = document.getElementsByTagName('input')[0];
var form = document.getElementsByTagName('form')[0];
Evt.on(input, 'click', function(evt){
    console.log('input click1');
    console.log(evt.target === input);
    console.log(evt.modified);
    //evt.stopPropagation();
    console.log(evt.modified);
});
var handle2 = Evt.on(input, 'click', function(evt){
    console.log('input click2');
    console.log(evt.target === input);
    console.log(evt.modified);
});
Evt.on(form, 'click', function(evt){
    console.log('form click');
    console.log(evt.currentTarget === input);
    console.log(evt.target === input);
    console.log(evt.currentTarget === form);
    console.log(evt.modified);
});
Evt.emit(input, 'click');
Evt.emit(input, 'click', {bubbles: true});
handle2.remove();
Evt.emit(input, 'click');
ログイン後にコピー

After 関数

ネイティブ オブジェクトにイベントを追加するプロセスは主に after 関数で行われます。この関数は主に次のことを行います。

  1. obj に応答関数がある場合は、それをディスパッチャー関数に置き換えます

  2. チェーン構造を使用して、複数のバインドされたイベント関数を順次実行できるようにします

  3. ハンドル オブジェクトを返します。これは次のように削除できます。 Remove メソッドの呼び出し このイベント バインディング

下の図は、after 関数が呼び出される前と後の onlog 関数の

引用を示しています

(呼び出し前)

(呼び出し後) )

詳細な説明については、

noteを参照してください。読者がそれに従って、再度実行できることを願っています

var after = function(target, method, cb, originalArgs){
    var existing = target[method];
    var dispatcher = existing;
    if (!existing || existing.target !== target) {
        //如果target中没有method方法,则为他添加一个方法method方法
        //如果target已经拥有method方法,但target[method]中target不符合要求则将method方法他替换
        dispatcher = target[method] = function(){
            //由于js是此法作用域:通过阅读包括变量定义在内的数行源码就能知道变量的作用域。
            //局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的
            //所以在这个函数中可以访问到dispatcher变量
            var results = null;
            var args = arguments;
            if (dispatcher.around) {//如果原先拥有method方法,先调用原始method方法
                //此时this关键字指向target所以不用target
                results = dispatcher.around.advice.apply(this, args);
            }

            if (dispatcher.after) {//如果存在after链则依次访问其中的advice方法
                var _after = dispatcher.after;
                while(_after && _after.advice) {
                    //如果需要原始参数则传入arguments否则使用上次执行结果作为参数
                    args = _after.originalArgs ? arguments : results;
                    results = _after.advice.apply(this, args);
                    _after = _after.next;
                }
            }
        }

        if (existing) {
        //函数也是对象,也可以拥有属性跟方法
        //这里将原有的method方法放到dispatcher中
            dispatcher.around = {
                advice: function(){
                    return existing.apply(target, arguments);
                }
            }
        }
        dispatcher.target = target;
    }

    var signal = {
        originalArgs: originalArgs,//对于每个cb的参数是否使用最初的arguments
        advice: cb,
        remove: function() {
            if (!signal.advice) {
                return;
            }
            //remove的本质是将cb从函数链中移除,删除所有指向他的链接
            var previous = signal.previous;
            var next = signal.next;
            if (!previous && !next) {
                dispatcher.after = signal.advice = null;
                dispatcher.target = null;
                delete dispatcher.after;
            } else if (!next){
                signal.advice = null;
                previous.next = null;
                signal.previous = null;
            } else if (!previous){
                signal.advice = null;
                dispatcher.after = next;
                next.previous = null;
                signal.next = null;
            } else {
                signal.advice = null;
                previous.next = next;
                next.previous = previous;
                signal.previous = null;
                signal.next = null;
            }
        }
    }

    var previous = dispatcher.after;
    if (previous) {//将signal加入到链式结构中,处理指针关系
        while(previous && previous.next && (previous = previous.next)){};
        previous.next = signal;
        signal.previous = previous;
    } else {//如果是第一次使用调用after方法,则dispatcher的after属性指向signal
        dispatcher.after = signal;
    }

    cb = null;//防止内存泄露
    return signal;
}
ログイン後にコピー

互換性を解決するために

IEブラウザIE9以降、DOM2イベント処理プログラムをサポートしています。ただし、古いバージョンの IE ブラウザでは、dom 要素 Event を追加するために、attachEvent メソッドが依然として使用されています。幸いなことに、Microsoft は 2016 年に IE8 のメンテナンスを終了すると発表しました。これは間違いなく、大多数のフロントエンド開発者にとって良いニュースです。ただし、夜明けが来る前に、DOM2 レベルのイベント ハンドラー をサポートしていないブラウザーは、通常、次の点に対処する必要があります:

  1. イベントを複数回バインドする、イベント処理関数の呼び出し順序

  2. イベント処理関数における this キーワードの指摘問題

  3. 標準化イベント イベント オブジェクトは、一般的に使用されるイベント属性をサポートします

attachEvent メソッドを使用してイベント処理関数を追加することはできないため、イベント処理関数の呼び出し順序を保証するために、attachEvent を放棄し、代わりに上記の after によって生成された正のシーケンス チェーン構造を使用しました。

//1、统一事件触发顺序
    function fixAttach(target, type, listener) {
    debugger;
        var listener = fixListener(listener);
        var method = 'on' + type;
        return after(target, method, listener, true);
    };
ログイン後にコピー

イベント処理関数の this キーワードについては、クロージャを通じて解決できます。 (ソース)、例:

この記事もこの方法でこの問題を解決します

//1、统一事件触发顺序
    function fixAttach(target, type, listener) {
    debugger;
        var listener = fixListener(listener);
        var method = 'on' + type;
        return after(target, method, listener, true);
    };

    function fixListener(listener) {
        return function(evt){
            //每次调用listenser之前都会调用fixEvent
            debugger;
            var e = _fixEvent(evt, this);//this作为currentTarget
            if (e && e.cancelBubble && (e.currentTarget !== e.target)){
                return;
            }
            var results =  listener.call(this, e);

            if (e && e.modified) {
                // 在整个函数链执行完成后将lastEvent回归到原始状态,
                //利用异步队列,在主程序执行完后再执行事件队列中的程序代码
                //常规的做法是在emit中判断lastEvent并设为null
                //这充分体现了js异步编程的优势,把变量赋值跟清除代码放在一起,避免逻辑分散,缺点是不符合程序员正常思维方式
                if(!lastEvent){
                    setTimeout(function(){
                        lastEvent = null;
                    });
                }
                lastEvent = e;
            }
            return results;
        }
    }
ログイン後にコピー

イベントの場合 オブジェクトの標準化のために、ie によって提供される既存の属性を標準のイベント属性

function _fixEvent(evt, sender){
        if (!evt) {
            evt = window.event;
        }
        if (!evt) { // emit没有传递事件参数,或者通过input.onclick方式调用
            return evt;
        }
        if(lastEvent && lastEvent.type && evt.type == lastEvent.type){
        //使用一个全局对象来保证在冒泡过程中访问的是同一个event对象
        //chrome中整个事件处理过程event是唯一的
            evt = lastEvent;
        }
        var fixEvent = evt;
        // bubbles 和cancelable根据每次emit时手动传入参数设置
        fixEvent.bubbles = typeof evt.bubbles !== 'undefined' ? evt.bubbles : false;
        fixEvent.cancelable = typeof evt.cancelable !== 'undefined' ? evt.cancelable : true;
        fixEvent.currentTarget = sender;
        if (!fixEvent.target){ // 多次绑定统一事件,只fix一次
            fixEvent.target = fixEvent.srcElement || sender;

            fixEvent.eventPhase = fixEvent.target === sender ? 2 : 3;
            if (!fixEvent.preventDefault) {
                fixEvent.preventDefault = _preventDefault;
                fixEvent.stopPropagation = _stopPropagation;
                fixEvent.stopImmediatePropagation = _stopImmediatePropagation;
            }
            //参考:http://www.php.cn/
            if( fixEvent.pageX == null && fixEvent.clientX != null ) {
                var doc = document.documentElement, body = document.body;
                fixEvent.pageX = fixEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - 
                (doc && doc.clientLeft || body && body.clientLeft || 0);
                fixEvent.pageY = fixEvent.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - 
                (doc && doc.clientTop  || body && body.clientTop  || 0);
            }
            if (!fixEvent.relatedTarget && fixEvent.fromEvent) {
                fixEvent.relatedTarget = fixEvent.fromEvent === fixEvent.target ? fixEvent.toElement : fixEvent.fromElement;
            }
            // 参考: http://www.php.cn/
            if (!fixEvent.which && fixEvent.keyCode) {
                fixEvent.which = fixEvent.keyCode;
            }
        }

        return fixEvent;
    }

    function _preventDefault(){
        this.defaultPrevented = true;
        this.returnValue = false;

        this.modified = true;
    }

    function _stopPropagation(){
        this.cancelBubble = true;

        this.modified = true;
    }

    function _stopImmediatePropagation(){
        this.isStopImmediatePropagation = true;
        this.modified = true;
    }
ログイン後にコピー

に変換する必要があります。 _preventDefault、_stopPropagation、および _stopImmediatePropagation の 3 つの関数では、呼び出された場合、リスナーは実行後に変数に保存されるため、後続のイベント ハンドラーはイベントに基づいて次の処理ステップを実行できます。この関数のシミュレーションでは、クロージャを通じて解決します。

上記の fixListener についても同様で、この形式に直接記述することはできないことに注意してください。

注意すべき点は、イベントを別の目的で標準化していることです。

Evt.emit(input, 'click');// のように、イベント プロセスを制御するパラメータを設定できます。バブリングなし

Evt.emit(input, 'click', {bubbles: true});//Bubbles

私のテストによると、イベントをトリガーするために fireEvent を使用してバブリングを防ぐことはできません。そこで、ここでは Javascript を使用してバブリングプロセスをシミュレートします。同時に、このプロセスではイベント オブジェクトの一意性も保証する必要があります。

// 模拟冒泡事件
    var sythenticBubble = function(target, type, evt){
        var method = 'on' + type;
        var args = Array.prototype.slice.call(arguments, 2);
        // 保证使用emit触发dom事件时,event的有效性
        if ('parentNode' in target) {
            var newEvent = args[0] = {};
            for (var p in evt) {
                newEvent[p] = evt[p];
            }

            newEvent.preventDefault = _preventDefault;
            newEvent.stopPropagation = _stopPropagation;
            newEvent.stopImmediatePropagation = _stopImmediatePropagation;
            newEvent.target = target;
            newEvent.type = type;
        }

        do{
            if (target && target[method]) {
                target[method].apply(target, args);
            }
        }while(target && (target = target.parentNode) && target[method] && newEvent && newEvent.bubbles);
    }

    var emit = function(target, type, evt){
        if (target.dispatchEvent && document.createEvent){
            var newEvent = document.createEvent('HTMLEvents');
            newEvent.initEvent(type, evt && !!evt.bubbles, evt && !!evt.cancelable);
            if (evt) {
                for (var p in evt){
                    if (!(p in newEvent)){
                        newEvent[p] = evt[p];
                    }
                }
            }

            target.dispatchEvent(newEvent);
        } /*else if (target.fireEvent) {
            target.fireEvent('on' + type);// 使用fireEvent在evt参数中设置bubbles:false无效,所以弃用
        } */else {
            return sythenticBubble.apply(on, arguments);
        }
    }
ログイン後にコピー

完全なコードを添付します:






Writing to Same Doc




  
ログイン後にコピー

BSD ライセンスに基づく

KityMinder . Powered by f-cube, FEX Source Bug |

JavaScript イベントの詳細な説明メカニズム互換性ソリューション グラフィックとテキストのコンテンツについては、PHP の中国語 Web サイト (www.php.cn) に注目してください。

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート