jQuery event 源码注释

jQuery event 源码注释

/*

* 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);

});