博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Javascript事件机制兼容性解决方案
阅读量:5965 次
发布时间:2019-06-19

本文共 10389 字,大约阅读时间需要 34 分钟。

原文:

  本文的解决方案可以用于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');
View Code

 

After函数

  

  为native对象添加事件的过程主要在after函数中完成,这个函数主要做了以下几件事:

  1. 如果obj中已有响应函数,将其替换成dispatcher函数
  2. 使用链式结构,保证多次绑定事件函数的顺序执行
  3. 返回一个handle对象,调用remove方法可以去除本次事件绑定

  下图为after函数调用前后onlog函数的引用

    

 

      (调用前)

  

      (调用后)

  详细解释请看注释,希望读者能够跟着运行一遍

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;}
View Code

  

解决兼容性

  IE浏览器从IE9开始已经支持DOM2事件处理程序,但是对于老版本的ie浏览器,任然使用attachEvent方式来为dom元素添加事件。值得庆幸的是微软已宣布2016年将不再对ie8进行维护,对于广大前端开发者无疑是一个福音。然而在曙光来临之前,仍然需要对那些不支持DOM2级事件处理程序的浏览器进行兼容性处理,通常需要处理以下几点:

  1. 多次绑定一个事件,事件处理函数的调用顺序问题
  2. 事件处理函数中的this关键字指向问题
  3. 标准化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);    };
View Code

 对于事件处理函数中的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;        }    }
View Code

  对于事件对象的标准化,我们需要将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.nowamagic.net/javascript/js_EventMechanismInDetail.php            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.cnblogs.com/hsapphire/archive/2009/12/18/1627047.html            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;    }
View Code

  _preventDefault_stopPropagation_stopImmediatePropagation三个函数中我们,如果被调用则listener执行完后使用一个变量保存event对象(见fixListener),以便后序事件处理程序根据event对象属性进行下一步处理。,对于这个函数的模拟,我们同样通过闭包来解决。

  

  

  注意这里不能直接写成这种形式,上文中fixListener也是同样道理。

  

  需要注意一点,我们将event标准化目的还有一点,可以在emit方法中设置参数来控制事件过程,比如:

  Evt.emit(input, 'click');//不冒泡

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

  根据我的测试使用fireEvent方式触发事件,无法设置{bubbles:false}来阻止冒泡,所以这里我们用Javascript来模拟冒泡过程。同时在这个过程中也要保证event对象的唯一性。

// 模拟冒泡事件    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);        }    }
View Code

  附上完整代码:

Writing to Same Doc
View Code

  :

  欢迎各位有志之士前来交流探讨!

参考文章:

 

你可能感兴趣的文章
AXD+H-JTAG环境搭建总结
查看>>
Oracle 项目就是那回事 ----表空间的管理
查看>>
Linux命令常用的快捷键
查看>>
推荐一款图片轮换jQuery插件
查看>>
Reading papers_6(Pattern Recognition And Machine Learning一书,ing...)
查看>>
HDU 3441 Rotation
查看>>
[zz]KVM 虚拟机故障排除一例
查看>>
hadoop 权威指南学习笔记ing(1)
查看>>
从代码中抽离数据的原则
查看>>
[Bootstrap] 8. 'Collapse', data-target, data-toggle & data-parent
查看>>
Android面向HTTP协议发送post请求
查看>>
125 Valid Palindrome
查看>>
湾区见闻
查看>>
php通过数组存取mysql查询语句的返回值
查看>>
oracle用户管理的完全恢复5:控制文件损坏(控制文件前后内容未改变)
查看>>
C#中的关键字
查看>>
【水】HDU 2099——整除的尾数
查看>>
adf常用方法总结
查看>>
MongoDB的C#驱动基本使用
查看>>
开源:秋式广告杀手源码
查看>>