jQuery event 源码注释

来源:互联网 发布:js 掷骰子动画 编辑:程序博客网 时间:2024/04/29 14:14

/* 
 * author:prk 
 * date:2008-08-17 
 * comment:analyse of jquery event 
 *  
 */ 
jQuery.event = {

    // add 事件到一个元素上。 
    add : function(elem, types, handler, data) { 
        if (elem.nodeType == 3 || elem.nodeType == 8)// 空白节点或注释 
            return;

        // IE不能传入window,先复制一下。 
        if (jQuery.browser.msie && elem.setInterval) 
            elem = window;

        // 为handler分配一个全局唯一的Id 
        if (!handler.guid) 
            handler.guid = this.guid++;

        // 把data附到handler.data中 
        if (data != undefined) { 
            var fn = handler; 
            handler = this.proxy(fn, function() {// 唯一Id,wrap原始handler Fn 
                        return fn.apply(this, arguments); 
                    }); 
            handler.data = data; 
        }

        // 初始化元素的events。如果没有取到events中值,就初始化data: {} 
        var events = jQuery.data(elem, "events") 
                || jQuery.data(elem, "events", {}), 
        // 如果没有取到handle中值,就初始化data: function() {....} 
        handle = jQuery.data(elem, "handle") 
                || jQuery.data(elem, "handle", function() { 
                    // 处理一个触发器的第二个事件和当page已经unload之后调用一个事件。 
                        if (typeof jQuery != "undefined" 
                                && !jQuery.event.triggered) 
                            return jQuery.event.handle.apply(// arguments.callee.elem=handle.elem 
                                    arguments.callee.elem, arguments); 
                    }); 
        // 增加elem做为handle属性,防止IE由于没有本地Event而内存泄露。 
        handle.elem = elem;

        // 处理采用空格分隔多个事件名,如jQuery(...).bind("mouseover mouseout", fn); 
        jQuery.each(types.split(/\s+/), function(index, type) { 
            // 命名空间的事件,一般不会用到。 
                var parts = type.split("."); 
                type = parts[0]; 
                handler.type = parts[1];

                // 捆绑到本元素type事件的所有处理函数 
                var handlers = events[type];

                if (!handlers) {// 没有找到处理函数列表就初始化事件队列 
                    handlers = events[type] = {};

                    // 如果type不是ready,或ready的setup执行返回false 
                    if (!jQuery.event.special[type] 
                            || jQuery.event.special[type].setup 
                                    .call(elem, data) === false) { 
                        // 调用系统的事件函数来注册事件 
                        if (elem.addEventListener)// FF 
                            elem.addEventListener(type, handle, false); 
                        else if (elem.attachEvent)// IE 
                            elem.attachEvent("on" + type, handle); 
                    } 
                }

                // 把处理器的id和handler形式属性对的形式保存在handlers列表中, 
                // 也存在events[type][handler.guid]中。 
                handlers[handler.guid] = handler;

                // 全局缓存这个事件的使用标识 
                jQuery.event.global[type] = true; 
            });

        // 防止IE内存泄露。 
        elem = null; 
    },

    guid : 1, 
    global : {},

    // 从元素中remove一个事件 
    remove : function(elem, types, handler) { 
        if (elem.nodeType == 3 || elem.nodeType == 8) 
            return; 
        // 取出元素的events中Fn列表 
        var events = jQuery.data(elem, "events"), ret, index;

        if (events) { 
            // remove所有的该元素的事件 .是命名空间的处理 
            if (types == undefined 
                    || (typeof types == "string" && types.charAt(0) == ".")) 
                for (var type in events) 
                    this.remove(elem, type + (types || "")); 
            else { 
                // types, handler参数采用{type:xxx,handler:yyy}形式 
                if (types.type) { 
                    handler = types.handler; 
                    types = types.type; 
                }

                // 处理采用空格分隔多个事件名 jQuery(...).unbind("mouseover mouseout", fn); 
                jQuery 
                        .each(types.split(/\s+/), function(index, type) { 
                            // 命名空间的事件,一般不会用到。 
                                var parts = type.split("."); 
                                type = parts[0];

                                if (events[type]) {// 事件名找到 
                                    if (handler)// handler传入,就remove事件名的这个处理函数 
                                        delete events[type][handler.guid];//guid的作用 
                                    else    // remove这个事件的所有处理函数,带有命名空间的处理 
                                        for (handler in events[type]) 
                                            if (!parts[1] 
                                                    || events[type][handler].type == parts[1]) 
                                                delete events[type][handler];

                                    // 如果没有该事件的处理函数存在,就remove事件名 
                                    for (ret in events[type])// 看看有没有? 
                                        break; 
                                    if (!ret) {// 没有 
                                        if (!jQuery.event.special[type]//不是ready 
                                                || jQuery.event.special[type].teardown 
                                                        .call(elem) === false) {// type不等于ready 
                                            if (elem.removeEventListener)// 在浏览器中remove事件名 
                                                elem.removeEventListener(type, 
                                                        jQuery.data(elem, 
                                                                "handle"), 
                                                        false); 
                                            else if (elem.detachEvent) 
                                                elem.detachEvent("on" + type, 
                                                        jQuery.data(elem, 
                                                                "handle")); 
                                        } 
                                        ret = null; 
                                        delete events[type];// 在缓存中除去。 
                                    } 
                                } 
                            }); 
            }

            // 不再使用,除去expando 
            for (ret in events) 
                break; 
            if (!ret) { 
                var handle = jQuery.data(elem, "handle"); 
                if (handle) 
                    handle.elem = null; 
                jQuery.removeData(elem, "events"); 
                jQuery.removeData(elem, "handle"); 
            } 
        } 
    },

    trigger : function(type, data, elem, donative, extra) { 
        data = jQuery.makeArray(data);

        if (type.indexOf("!") >= 0) {// 支持!的not的操作如!click,除click之后的所有 
            type = type.slice(0, -1);// 除最后一个字符? 
            var exclusive = true; 
        }

        if (!elem) {// 处理全局的fire事件 
            if (this.global[type]) 
                jQuery.each(jQuery.cache, function() { 
                    // 从cache中找到所有注册该事件的元素,触发改事件的处理函数 
                        if (this.events && this.events[type]) 
                            jQuery.event.trigger(type, data, this.handle.elem); 
                    }); 
        } else {// 处理单个元素事件的fire事件 
            if (elem.nodeType == 3 || elem.nodeType == 8) 
                return undefined;

            var val, ret, fn = jQuery.isFunction(elem[type] || null), 
            // 我们是否要提交一个伪造的事件? 
            event = !data[0] || !data[0].preventDefault;

            // 构建伪造的事件。 
            if (event) { 
                data.unshift( {//存到数组中的第一个 
                    type : type, 
                    target : elem, 
                    preventDefault : function() { 
                    }, 
                    stopPropagation : function() { 
                    }, 
                    timeStamp : now() 
                }); 
                data[0][expando] = true; // 不需要修正伪造事件 
            }

            //防止事件名出错 
            data[0].type = type; 
            if (exclusive) 
                data[0].exclusive = true;

            // 触发事件 
            var handle = jQuery.data(elem, "handle"); 
            if (handle) 
                val = handle.apply(elem, data);

            // Handle triggering native .onfoo handlers (and on links since we 
            // don't call .click() for links) 
            //处理触发.onfoo这样的本地处理方法,但是是对于links 's .click()不触发 
            if ((!fn || (jQuery.nodeName(elem, 'a') && type == "click")) 
                    && elem["on" + type]&& elem["on" + type].apply(elem, data) === false)
                val = false;

            // Extra functions don't get the custom event object 
            if (event) 
                data.shift();

            // 处理触发extra事件 
            if (extra && jQuery.isFunction(extra)) { 
                //执行extra,同时把结果存到data中。 
                ret = extra.apply(elem, val == null ? data : data.concat(val)); 
                // if anything is returned, give it precedence and have it 
                // overwrite the previous value 
                if (ret !== undefined) 
                    val = ret; 
            }

            // 触发本地事件 
            if (fn && donative !== false && val !== false 
                    && !(jQuery.nodeName(elem, 'a') && type == "click")) { 
                this.triggered = true; 
                try { 
                    elem[type](); 
                    //对于一些hidden的元素,IE会报错 
                } catch (e) { 
                } 
            }

            this.triggered = false; 
        }

        return val; 
    },

    handle : function(event) { 
        // 返回 undefined or false 
        var val, ret, namespace, all, handlers;

        event = arguments[0] = jQuery.event.fix(event || window.event);

        // 命名空间处理 
        namespace = event.type.split("."); 
        event.type = namespace[0]; 
        namespace = namespace[1]; 
        // all = true 表明任何 handler 
        all = !namespace && !event.exclusive; 
        // 找到元素的events中缓存的事件名的处理函数列表 
        handlers = (jQuery.data(this, "events") || {})[event.type];

        for (var j in handlers) {// 每个处理函数执行 
            var handler = handlers[j];

            // Filter the functions by class 
            if (all || handler.type == namespace) { 
                // 传入引用,为了之后删除它们 
                event.handler = handler; 
                event.data = handler.data;

                ret = handler.apply(this, arguments);// 执行事件处理函数

                if (val !== false) 
                    val = ret;// 只要有一个处理函数返回false,本函数就返回false.

                if (ret === false) {// 不执行浏览器默认的动作 
                    event.preventDefault(); 
                    event.stopPropagation(); 
                } 
            } 
        }

        return val; 
    },

    props : "altKey attrChange attrName bubbles button cancelable charCode clientX " 
            + "clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode " 
            + "metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX " 
            + "screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which" 
                    .split(" "),

    //对事件进行包裹。 
    fix : function(event) { 
        if (event[expando] == true)//表明事件已经包裹过 
            return event;

        //保存原始event,同时clone一个。 
        var originalEvent = event; 
        event = { 
            originalEvent : originalEvent 
        };

        for (var i = this.props.length, prop;i;) { 
            prop = this.props[--i]; 
            event[prop] = originalEvent[prop]; 
        } 
         
        event[expando] = true; 
         
        //加上preventDefault and stopPropagation,在clone不会运行 
        event.preventDefault = function() { 
            // 在原始事件上运行 
            if (originalEvent.preventDefault) 
                originalEvent.preventDefault(); 
             
            originalEvent.returnValue = false; 
        }; 
        event.stopPropagation = function() { 
            // 在原始事件上运行 
            if (originalEvent.stopPropagation) 
                originalEvent.stopPropagation(); 
             
            originalEvent.cancelBubble = true; 
        };

        // 修正 timeStamp 
        event.timeStamp = event.timeStamp || now();

        // 修正target 
        if (!event.target) 
            event.target = event.srcElement || document;             
        if (event.target.nodeType == 3)//文本节点是父节点。 
            event.target = event.target.parentNode;

        // relatedTarget 
        if (!event.relatedTarget && event.fromElement) 
            event.relatedTarget = event.fromElement == event.target 
                    ? event.toElement 
                    : event.fromElement;

        // Calculate pageX/Y if missing and clientX/Y available 
        if (event.pageX == null && event.clientX != null) { 
            var doc = document.documentElement, body = document.body; 
            event.pageX = event.clientX 
                    + (doc && doc.scrollLeft || body && body.scrollLeft || 0) 
                    - (doc.clientLeft || 0); 
            event.pageY = event.clientY 
                    + (doc && doc.scrollTop || body && body.scrollTop || 0) 
                    - (doc.clientTop || 0); 
        }

        // Add which for key events 
        if (!event.which 
                && ((event.charCode || event.charCode === 0) 
                        ? event.charCode 
                        : event.keyCode)) 
            event.which = event.charCode || event.keyCode;

        // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) 
        if (!event.metaKey && event.ctrlKey) 
            event.metaKey = event.ctrlKey;

        // Add which for click: 1 == left; 2 == middle; 3 == right 
        // Note: button is not normalized, so don't use it 
        if (!event.which && event.button) 
            event.which = (event.button & 1 ? 1 : (event.button & 2 
                    ? 3 
                    : (event.button & 4 ? 2 : 0)));

        return event; 
    },

    proxy : function(fn, proxy) { 
        // 作用就是分配全局guid. 
        proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++; 
        return proxy; 
    },

    special : { 
        ready : { 
            // Make sure the ready event is setup 
            setup : bindReady, 
            teardown : function() { 
            } 
        } 
    } 
};

if (!jQuery.browser.msie) { 
    // Checks if an event happened on an element within another element 
    // Used in jQuery.event.special.mouseenter and mouseleave handlers 
    var withinElement = function(event) { 
        // Check if mouse(over|out) are still within the same parent element 
        var parent = event.relatedTarget; 
        // Traverse up the tree 
        while (parent && parent != this) 
            try { 
                parent = parent.parentNode; 
            } catch (e) { 
                parent = this; 
            }

        if (parent != this) { 
            // set the correct event type 
            event.type = event.data; 
            // handle event if we actually just moused on to a non sub-element 
            jQuery.event.handle.apply(this, arguments); 
        } 
    };

    jQuery.each( { 
        mouseover : 'mouseenter', 
        mouseout : 'mouseleave' 
    }, function(orig, fix) { 
        jQuery.event.special[fix] = { 
            setup : function() { 
                jQuery.event.add(this, orig, withinElement, fix); 
            }, 
            teardown : function() { 
                jQuery.event.remove(this, orig, withinElement); 
            } 
        }; 
    }); 
}

jQuery.fn.extend( { 
    bind : function(type, data, fn) { 
        return type == "unload" ? this.one(type, data, fn) : this 
                .each(function() {// fn || data, fn && data实现了data参数可有可无 
                    jQuery.event.add(this, type, fn || data, fn && data); 
                }); 
    },

        // 为每一个匹配元素的特定事件(像click)绑定一个一次性的事件处理函数。 
        // 在每个对象上,这个事件处理函数只会被执行一次。其他规则与bind()函数相同。 
        // 这个事件处理函数会接收到一个事件对象,可以通过它来阻止(浏览器)默认的行为。 
        // 如果既想取消默认的行为,又想阻止事件起泡,这个事件处理函数必须返回false。 
        one : function(type, data, fn) { 
            var one = jQuery.event.proxy(fn || data, function(event) { 
                jQuery(this).unbind(event, one); 
                return (fn || data).apply(this, arguments);// this-->当前的元素 
                }); 
            return this.each(function() { 
                jQuery.event.add(this, type, one, fn && data); 
            }); 
        },

        // bind()的反向操作,从每一个匹配的元素中删除绑定的事件。 
        // 如果没有参数,则删除所有绑定的事件。 
        // 你可以将你用bind()注册的自定义事件取消绑定。 
        // I如果提供了事件类型作为参数,则只删除该类型的绑定事件。 
        // 如果把在绑定时传递的处理函数作为第二个参数,则只有这个特定的事件处理函数会被删除。 
        unbind : function(type, fn) { 
            return this.each(function() { 
                jQuery.event.remove(this, type, fn); 
            }); 
        },

     
        trigger : function(type, data, fn) { 
            return this.each(function() { 
                jQuery.event.trigger(type, data, this, true, fn); 
            }); 
        }, 
        //这个特别的方法将会触发指定的事件类型上所有绑定的处理函数。但不会执行浏览器默认动作. 
        triggerHandler : function(type, data, fn) { 
            return this[0] 
                    && jQuery.event.trigger(type, data, this[0], false, fn); 
        }, 
         
        //每次点击后依次调用函数。 
        toggle : function(fn) {      
            var args = arguments, i = 1; 
             
            while (i < args.length)//每个函数分配GUID 
                jQuery.event.proxy(fn, args[i++]);

            return this.click(jQuery.event 
                    .proxy(fn, function(event) {//分配GUID                     
                            this.lastToggle = (this.lastToggle || 0) % i;//上一个函数                             
                            event.preventDefault();//阻止缺省动作 
                            //执行参数中的第几个函数,apply可以采用array-like的参数 
                            //With apply, you can use an array literal,  
                            //for example, fun.apply(this, [name, value]), 
                            //or an Array object, for example, fun.apply(this, new Array(name, value)).  
                            return args[this.lastToggle++].apply(this, 
                                    arguments) || false; 
                        })); 
        }, 
         
        //一个模仿悬停事件(鼠标移动到一个对象上面及移出这个对象)的方法。 
        //这是一个自定义的方法,它为频繁使用的任务提供了一种“保持在其中”的状态。 
        //当鼠标移动到一个匹配的元素上面时,会触发指定的第一个函数。当鼠标移出这个元素时, 
        //会触发指定的第二个函数。而且,会伴随着对鼠标是否仍然处在特定元素中的检测(例如,处在div中的图像), 
        //如果是,则会继续保持“悬停”状态,而不触发移出事件(修正了使用mouseout事件的一个常见错误)。 
        hover : function(fnOver, fnOut) { 
            return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut); 
        }, 
         
        //dom ready时执行 fn 
        ready : function(fn) {           
            bindReady();//注册监听           
            if (jQuery.isReady)//ready就运行                
                fn.call(document, jQuery);           
            else 
                // 增加这个函数到queue中。可见支持无数的ready的调用。 
                jQuery.readyList.push(function() { 
                    return fn.call(this, jQuery); 
                });

            return this; 
        } 
    });

jQuery.extend( { 
    isReady : false, 
    readyList : [], 
    // Handle when the DOM is ready 
        ready : function() {             
            if (!jQuery.isReady) {       
                jQuery.isReady = true; 
                 
                if (jQuery.readyList) {                  
                    jQuery.each(jQuery.readyList, function() { 
                        this.call(document); 
                    });              
                    jQuery.readyList = null; 
                } 
                 
                jQuery(document).triggerHandler("ready"); 
            } 
        } 
    });

var readyBound = false;

function bindReady() { 
    if (readyBound) 
        return; 
    readyBound = true;

    // Mozilla, Opera, webkit nightlies 支持DOMContentLoaded事件     
    if (document.addEventListener && !jQuery.browser.opera) 
        //当DOMContentLoaded事件触发时就运行jQuery.ready 
        document.addEventListener("DOMContentLoaded", jQuery.ready, false);

    //IE或不是frame的window 
    if (jQuery.browser.msie && window == top) 
        (function() { 
            if (jQuery.isReady) 
                return; 
            try { 
                // 在ondocumentready之前,一直都会抛出异常               
                // http://javascript.nwbox.com/IEContentLoaded/ 
                document.documentElement.doScroll("left"); 
            } catch (error) { 
                //一直运行bindReady()(=arguments.callee) 
                setTimeout(arguments.callee, 0); 
                return; 
            }            
            jQuery.ready();//documentready就运行jQuery.ready 
        })();

    if (jQuery.browser.opera) 
        document.addEventListener("DOMContentLoaded", function() { 
            if (jQuery.isReady) 
                return; 
                //只有styleSheets完全enable时,才是完全的load,其实还有pic 
            for (var i = 0;i < document.styleSheets.length; i++) 
                if (document.styleSheets[i].disabled) {//通过styleSheets来判断 
                    setTimeout(arguments.callee, 0); 
                    return; 
                }            
                jQuery.ready(); 
            }, false);

    if (jQuery.browser.safari) { 
        var numStyles; 
        (function() { 
            if (jQuery.isReady) 
                return; 
                //首先得得到readyState=loaded或=complete 
            if (document.readyState != "loaded" 
                    && document.readyState != "complete") { 
                setTimeout(arguments.callee, 0); 
                return; 
            } 
            //取得style的length,比较它们之间的长度,看看是不是完成loaded 
            if (numStyles === undefined) 
                numStyles = jQuery("style, link[rel=stylesheet]").length; 
            if (document.styleSheets.length != numStyles) { 
                setTimeout(arguments.callee, 0); 
                return; 
            }            
            jQuery.ready(); 
        })(); 
    }

    //最后只能依赖于window.load. 
    jQuery.event.add(window, "load", jQuery.ready); 
}

//为jquery对象增加常用的事件方法 
jQuery 
        .each( 
                ("blur,focus,load,resize,scroll,unload,click,dblclick," 
                        + "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + "submit,keydown,keypress,keyup,error") 
                        .split(","), function(i, name) {                     
                jQuery.fn[name] = function(fn) { 
                    return fn ? this.bind(name, fn) : this.trigger(name); 
                }; 
            });

// Prevent memory leaks in IE 
// And prevent errors on refresh with events like mouseover in other browsers 
// Window isn't included so as not to unbind existing unload events 
jQuery(window).bind('unload', function() { 
    for (var id in jQuery.cache) 
        // Skip the window 
        if (id != 1 && jQuery.cache[id].handle) 
            jQuery.event.remove(jQuery.cache[id].handle.elem); 
});


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/liuwei_blog/archive/2008/11/25/3366985.aspx


原创粉丝点击