关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » JavaScript »JavaScript事件机制兼容性解决方案

    JavaScript事件机制兼容性解决方案

    2015-03-09 00:00:00 出处:codeceo
    分享

    本文的解决方案可以用于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函数中完成,这个函数主要做了以下几件事:

    假如obj中已有响应函数,将其替换成dispatcher函数 使用链式结构,保证多次绑定事件函数的顺序执行 返回一个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;
    }

    解决兼容性

    IE浏览器从IE9开始已经支持DOM2事件处理程序,但是对于老版本的ie浏览器,任然使用attachEvent方式来为dom元素添加事件。值得庆幸的是微软已宣布2016年将不再对ie8进行维护,对于广大前端开发者无疑是一个福音。然而在曙光来临之前,仍然需要对那些不支持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);
        };

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

    在_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对象的唯一性。

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

    附上完整代码:

    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
    <meta http-equiv="window-target" content="_top">
    <title>Writing to Same Doc</title>
    <script language="JavaScript">
    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;
    }
    
    //1、统一事件触发顺序
    //2、标准化事件对象
    //3、模拟冒泡 emit时保持冒泡行为,注意input.onclick这种方式是不冒泡的
    //4、保持冒泡过程中event的唯一性
    
    window.Evt = (function(){
        var on = function(target, type, listener){
        debugger;
            if (!listener){
                return;
            }
            // 处理stopImmediatePropagation,通过包装listener来支持stopImmediatePropagation
            if (!(window.Event && window.Event.prototype && window.Event.prototype.stopImmediatePropagation)) {
                listener = _addStopImmediate(listener);
            }
    
            if (target.addEventListener) {
                target.addEventListener(type, listener, false);
    
                return {
                    remove: function(){
                        target.removeEventListener(type, listener);
                    }
                }
            } else {
                return fixAttach(target, type, listener);
            }
        };
        var lastEvent; // 使用全局变量来保证一个元素的多个listenser中事件对象的一致性,冒泡过程中事件对象的一致性;在chrome这些过程中使用的是同一个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.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;
        }
    
        function _addStopImmediate(listener) {
            return function(evt) { // 除了包装listener外,还要保证所有的事件函数共用一个evt对象
                if (!evt.isStopImmediatePropagation) {
                    //evt.stopImmediatePropagation = _stopImmediateProgation;
                    return listener.apply(this, arguments);
                }
            }
        }
    
        // 模拟冒泡事件
        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);
            }
        }
    
        return {
            on: on,
            emit: emit
        };
    })()
    </script>
    <style type="text/css"></style>
    </head>
    <body>
      <form>
        <input type="button" value="Replace Content" >
      </form>
    </body>
    </html>

    脑图:

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

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    Windows 11 版本与 Windows 10比较,新功能一览

    Windows 11激活产品密钥收集及专业版激活方法

    IT头条

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    严重缩水!NVIDIA将推中国特供RTX 5090 DD:只剩24GB显存

    00:17

    无线路由大厂 TP-Link突然大裁员:补偿N+3

    02:39

    Meta 千万美金招募AI高级人才

    00:22

    技术热点

    微软已修复windows 7/windows 8.1媒体中心严重漏洞 用户可下载安

    卸载MySQL数据库,用rpm如何实现

    windows 7中使用网上银行或支付宝支付时总是打不开支付页面

    一致性哈希算法原理设计

    MySQL数字类型中的三种常用种类

    如何解决SQL Server中传入select语句in范围参数

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。