jQ的DOM事件机制: 原生event的封装
发布在# 菜鸟解读 jQuery #2014年7月2日view:10309
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

本篇带来的是jQ对原生事件的包装,
先来看个栗子

$(document).bind('click',function(e){
    var e = e || window.event,
        tar = e.target || e.srcElement;
    alert(tar);
});

上面这段代码看起来似乎没什么问题,也考虑到的兼容性的event对象,
但其实,是有问题的:根本不用进行兼容性检测.
使用jQ绑定的事件,接受的e参数是被处理过的,并非浏览器基于的原生event,
下面我们来看下jQ对于event是肿么样的处理:
首先浏览器触发了事件,jQ捆绑在dom上的函数接受到了原生的event对象,
然后交给jQuery.event.fix(),这里处理了兼容性的差异,
下一步又交给了jQuery.Event(),这里会返回一个jQ对象,
然后又回到jQuery.event.fix(),将原生事件的一些属性绑定到jQ对象,并处理兼容性属性问题.
然后出发真正的用户绑定上的函数,将处理好的event对象传递进去。
下面的代码,小编建议先从fix()函数看起。

jQuery.event = {
    //...
    //事件对象的公共属性
    props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
    //键盘事件对象的属性和修正方法
    keyHooks: {
        props: "char charCode key keyCode".split(" "),
        filter: function( event, original ) {
            // 关于上面这4个属性,兼容性到ie6的是keyCode,其他的都是ie9+支持
            if ( event.which == null ) {
                event.which = original.charCode != null ? original.charCode : original.keyCode;
            }
            return event;
        }
    },
    //鼠标事件对象的属性和修正方法
    mouseHooks: {
        props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
        filter: function( event, original ) {
            var eventDoc, doc, body,
                button = original.button,
                fromElement = original.fromElement;
            if ( event.pageX == null && original.clientX != null ) {
                eventDoc = event.target.ownerDocument || document;
                doc = eventDoc.documentElement;
                body = eventDoc.body;
                //pageX,pageY 表示距离文档左上角的坐标,这个属性ie9-不支持
                //jQ进行了响应的计算
                event.pageX = original.clientX + 
                    ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - 
                    ( doc && doc.clientLeft || body && body.clientLeft || 0 );
                event.pageY = original.clientY + 
                    ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - 
                    ( doc && doc.clientTop  || body && body.clientTop  || 0 );
            }
            //relatedTarget属性指的是和目标元素相关的元素对象
            //鼠标离开事件:目标元素是离开的元素, 相关元素是离开后进入到的元素
            //鼠标移入事件:目标元素是进入的元素, 相关元素是从哪个元素离开的那个元素
            if ( !event.relatedTarget && fromElement ) {
                event.relatedTarget = fromElement === event.target ? 
                    original.toElement : 
                    fromElement;
            }
            // ie低版本浏览器中, 没有which属性,button的值是142,然后根据button的值对which进行修正
            // 修正后的which的值是 1 2 3
            // 关于鼠标点击比较乱,建议大家以后使用jQ这种修正后的which属性
            if ( !event.which && button !== undefined ) {
                event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
            }
            return event;
        }
    },
    //把原生事件对象封装为jQ事件对象,并修正不兼容属性
    fix: function( event ) {
        //经过jQ处理的对象,JQ会为其绑定expando属性,
        //如果event已经是jQ的了,就不必做处理了,直接返回即可
        if ( event[ jQuery.expando ] ) {
            return event;
        }
        var i, prop,
            originalEvent = event,
            //获取对应的修正对象
            //所有类型的事件对应的fixHooks修正对象只有两种: 
            //jQuery.event.keyHooks || jQuery.event.mouseHooks
            fixHook = jQuery.event.fixHooks[ event.type ] || {},
            //copy最终都是事件应该存在的属性,共有的加上两大分类各自私有的
            copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;

        //生成一个jQ的对象,这里大家先看jQuery.Event的执行逻辑,然后再回到这里
        event = jQuery.Event( originalEvent );

        //将原生事件对象上的属性都复制到jQEvent对象上
        for ( i = copy.length; i; ) {
            prop = copy[ --i ];
            event[ prop ] = originalEvent[ prop ];
        }
        //ie9下,事件源是srcElement属性
        if ( !event.target ) {
            event.target = originalEvent.srcElement || document;
        }
        //如果事件源指向的是文本节点,则修正为其父元素节点
        if ( event.target.nodeType === 3 ) {
            event.target = event.target.parentNode;
        }
        // metaKey 指的是键盘上的特殊键,ie9下不支持,这里赋值为了ctrlKey
        if ( event.metaKey === undefined ) {
            event.metaKey = event.ctrlKey;
        }
        //调用修正对象的filter方法
        return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
    },
    //...
}
//src 原生事件类型 || 自定义事件类型 || 原生事件对象 || jQ事件对象
//props 其中的属性将被设置到新创建的jQ事件对象
jQuery.Event = function( src, props ) {
    //这里的写法是为了兼容两种形式来进行调用函数
    //new fn(); fn();
    if ( !(this instanceof jQuery.Event) ) {
        return new jQuery.Event( src, props );
    }
    // Event object
    if ( src && src.type ) {
        //原始 事件对象,这里是进行了备份
        this.originalEvent = src;
        //事件类型
        this.type = src.type;
        //返回 是否event对象被设置了阻止默认事件
        this.isDefaultPrevented = ( 
            src.defaultPrevented || 
            src.returnValue === false ||
            src.getPreventDefault && 
            src.getPreventDefault() 
        ) ? returnTrue : returnFalse;
    // Event type
    } else {
        this.type = src;
    }

    // 将传入进来的对象的属性复制给jQ事件对象
    if ( props ) {
        jQuery.extend( this, props );
    }
    // src是事件类型,则把 当前时间 给timeStamp变量
    // src是原生事件对象,则把 属性timeStamp 给timeStamp变量
    // DOM2的事件模型中,timeStamp属性是一个date对象,
    // DOM3的事件模型中,timeStamp属性是数值
    this.timeStamp = src && src.timeStamp || jQuery.now();
    // 设置jQ的标示符
    // jQuery.expando : '1712312312123'
    this[ jQuery.expando ] = true;
};
function returnFalse(){
    return false;
}
function returnTrue(){
    return true;
}
//事件原型,针对阻止默认事件,事件传播封装了兼容性调用方法
jQuery.Event.prototype = {
    //阻止浏览器默认事件
    preventDefault: function() {
        //设置属性为true
        this.isDefaultPrevented = returnTrue;
        var e = this.originalEvent;
        if ( !e )
            return;
        // 标准浏览器的阻止默认事件
        if ( e.preventDefault ) {
            e.preventDefault();
        } else {
            e.returnValue = false;
        }
    },
    //阻止事件冒泡
    stopPropagation: function() {
        this.isPropagationStopped = returnTrue;
        var e = this.originalEvent;
        if ( !e ) {
            return;
        }
        // 标准浏览器
        if ( e.stopPropagation ) {
            e.stopPropagation();
        }
        // ie
        e.cancelBubble = true;
    },
    //立刻停止事件执行和事件传播
    stopImmediatePropagation: function() {
        this.isImmediatePropagationStopped = returnTrue;
        this.stopPropagation();
    },
    isDefaultPrevented: returnFalse,
    isPropagationStopped: returnFalse,
    isImmediatePropagationStopped: returnFalse
};

本篇到此就结束了 , 不知道小编本篇的讲解是否到位..
感谢您的阅读 , 欢迎留言: 斧正, 交流, 提问

评论
发表评论
暂无评论
WRITTEN BY
前端狮子
JS前端开发工程师 :喜欢研究js,nodejs,html5; 希望结交更多朋友~
TA的新浪微博
PUBLISHED IN
# 菜鸟解读 jQuery #

本栏解读的jQ为1.7.2版本。 本人也是刚开始读起源码,在这里分享下成长的心得。 本人能力有限,也是接触JS不久的初学者,定会有不少解析不全不够明朗【甚至BUG】的地方, 希望各位牛牛多多留言斧正 感谢阅读 ps:由于工作不定时繁忙,本人也无法定期更新,但是会尽量抽时间学习,分享给大家

友情链接 大搜车前端团队博客
我的收藏