首頁 > web前端 > js教程 > 主體

詳解JavaScript事件機制相容性解決方案的程式碼圖文

黄舟
發布: 2017-03-30 16:34:56
原創
1043 人瀏覽過

本文的解決方案可以用在Javascript native物件和宿主物件(dom元素),透過以下的方式來綁定和觸發事件

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函數

為native物件新增事件的過程主要在after函數中完成,這個函數主要做了以下幾件事:

  1. 如果obj中已有回應函數,將其替換成dispatcher函數

  2. 使用鍊式結構,保證多次綁定事件函數的順序執行

  3. #傳回一個handle對象,呼叫remove方法可以移除本次事件綁定

#下圖為after函數呼叫前後onlog函數的參考

    (呼叫前)
  1. (呼叫後)

    詳細解釋請看
  2. 註釋
  3. ,希望讀者能夠跟著運行一遍

    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瀏覽器,任然使用attachEvent方式來為dom元素添加事件。在曙光來臨之前,仍然需要對那些不支援

DOM2級事件處理程序的瀏覽器進行相容性處理,通常需要處理以下幾點:

多次綁定一個事件,事件處理函數的呼叫順序問題

事件處理函數中的this關鍵字指向問題

標準化event事件對象,支援常用的事件屬性

由於使用attachEvent方法添加事件處理函數無法保證事件處理函數的呼叫順序,所以我們棄用attachEvent,轉而用上文中的after生成的正序鍊式結構來解決這個問題。

本文也是透過這種方式解決此問題

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

對於事件物件的標準化,我們需要將ie提供給我們的現有屬性轉換為標準的事件屬性。

在_preventDefault、_stopPropagation、_stopImmediatePropagation三個函數中我們,如果被呼叫則listener執行完後使用一個變數保存event物件(見fixListener),以便後序事件處理程序根據event物件屬性進行下一步處理。 stopImmediatePropagation函數,對於這個函數的模擬,我們同樣透過閉包來解決。

注意這裡不能直接寫成這個形式,上文中fixListener也是同樣道理。

需要注意一點,我們將event標準化目的還有一點,可以在emit方法中設定參數來控制事件過程,例如:
Evt. emit(input, 'click');//不冒泡

###Evt.emit(input, 'click', {bubbles: true});//冒泡#######根據我的測試使用fireEvent方式觸發事件,無法設定{bubbles:false}來阻止冒泡,所以這裡我們用Javascript來模擬冒泡過程。同時在這個過程中也要保證event物件的唯一性。 ###
//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;
        }
    }
登入後複製
###附上完整代碼:###
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;
    }
登入後複製
###腦圖:######KityMinder under BSD License . Powered by f-cube, FEX | Source Bug | Contact Us#### ## 以上就是######詳解JavaScript事件機制相容性解決方案的程式碼圖文的內容,更多相關內容請關注PHP中文網(www.php.cn)! ######
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!