JavaScript 이벤트 메커니즘 호환성 솔루션을 자세히 설명하는 코드 그래픽 및 텍스트

黄舟
풀어 주다: 2017-03-30 16:34:56
원래의
1106명이 탐색했습니다.

이 문서의 솔루션은 Javascript 기본객체 그리고 호스트 객체(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

함수

네이티브 객체에 이벤트를 추가하는 과정은 주로 after 함수에서 완료됩니다.

  1. obj에 응답 함수가 있는 경우 이를 디스패처 함수로 대체

  2. 체인 구조를 사용하여 여러 바인딩된 이벤트 함수의 순차적 실행 보장

  3. remove 메소드를 호출하면 이 이벤트 바인딩을 제거할 수 있습니다.

아래 그림은 전후의 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 브라우저DOM2 IE9이벤트 처리프로그램부터 지원되었지만 이전 버전의 IE 브라우저에서는 DOM 요소에 이벤트를 추가하는 데 여전히 attachmentEvent 메서드가 사용됩니다. 다행히 Microsoft는 2016년에 더 이상 IE8을 유지하지 않을 것이라고 발표했습니다. 이는 대부분의 프런트 엔드 개발자에게 의심할 여지 없이 좋은 소식입니다. DOM2 수준 이벤트 핸들러를 지원하지 않는 브라우저에 대한 호환성은 여전히 ​​처리되어야 하며 일반적으로 다음 사항이 필요합니다. be handler:

  1. more 한 번에 이벤트를 바인딩하면 이벤트 처리 함수의 호출 순서가 문제입니다

  2. this 키워드는 이벤트 처리 기능이 문제를 지적

  3. 표준화된 이벤트 이벤트 객체, 일반적으로 사용되는 이벤트 속성을 지원합니다

AttachEvent를 사용하여 이벤트 처리 기능을 추가했기 때문에 메소드는 이벤트 처리 함수의 호출 순서를 보장할 수 없으므로, 우리는 attachmentEvent를 포기하고 대신 위의 생성 이후를 사용했습니다.

//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에서 제공하는 기존 속성을 표준 이벤트 속성으로 변환해야 합니다.

_preventDefault, _stopPropagation 및 _stopImmediatePropagation의 세 가지 함수에서 호출되면 리스너는 실행 후 이벤트 개체를 저장하기 위해 변수를 사용합니다(fixListener 참조). ), 후속 이벤트 핸들러가 이벤트 객체 속성에 따라 다음 단계를 수행할 수 있도록 합니다. stopImmediatePropagation 함수는 이 함수의 시뮬레이션을 위해 클로저를 통해 해결됩니다.

위의 fixListener도 마찬가지입니다.

우리는 다른 목적을 위해 이벤트를 표준화한다는 점에 유의해야 합니다. 다음과 같이 Emit 메소드에 매개변수를 설정하여 이벤트 프로세스를 제어할 수 있습니다. Evt.emit(input, 'click');//거품 없음

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

내 의견에 따르면 테스트에서는 이벤트를 트리거하기 위해 fireEvent를 사용합니다. 버블링을 방지하기 위해 {bubbles:false}를 설정할 수 없으므로 여기서는 버블링 프로세스를 시뮬레이션하기 위해 Javascript를 사용합니다. 동시에 이 과정에서 이벤트 객체의 고유성도 보장되어야 합니다.

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;
    }
로그인 후 복사

전체 코드가 첨부되어 있습니다:

// 模拟冒泡事件
    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);
        }
    }
로그인 후 복사

뇌 지도:

BSD 라이선스에 따른 KityMinder Powered by f-cube, Source Bug |

위 내용은 자바스크립트 이벤트 메커니즘 호환 솔루션을 자세히 설명하는

코드와 그림의 내용입니다. 더 많은 관련 내용은 PHP 중국어 홈페이지(www.php.cn)를 참고해주세요!

원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿