前言:
这篇依旧长,请耐心看下去。
一、事件委托
DOM 有个 事件流 特性,所以触发 DOM 节点的时候,会经历 3 个阶段:
(1)阶段一:Capturing 事件捕获(从祖到目标)
在 事件
自上(document->html->body->xxx)而下到达目标节点的过程中,浏览器会检测 针对该事件的 监听器(用来捕获事件),并运行 捕获事件的监听器。
(2)阶段二:Target 目标
浏览器找到监听器后,就运行该监听器
(3)阶段三:Bubbling 冒泡(目标到祖)
在 事件
自下而上(document->html->body->xxx)到达目标节点的过程中,浏览器会检测 不是 针对该事件的 监听器(用来捕获事件),并运行 非捕获事件的监听器。
二、$()
.click()
作用:
为目标元素绑定点击事件
源码:
// 这种写法还第一次见,将所有鼠标事件写成字符串再换成数组
// 再一一绑定到 DOM 节点上去
// 源码 10969 行
jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick" +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave" +
"change select submit keydown keypress keyup contextmenu" ).split(" "),
function(i, name) {
// 事件绑定
// Handle event binding
jQuery.fn[name] = function(data, fn) {
return arguments.length > 0 ?
// 如果有参数的话,就用 jQuery 的 on 绑定
this.on(name, null, data, fn) :
// 否则使用 trigger
this.trigger(name);
};
} );
解析:
可以看到,jQuery 将所有的鼠标事件都一一列举了出来,并通过jQuery.fn[name] = function(data, fn) {xxx}
如果有参数,则是绑定事件,调用 on() 方法;
没有参数,则是调用事件,调用 trigger() 方法(trigger() 放到下篇讲)
三、$()
.on()
作用:
在被选元素及子元素上添加一个或多个事件处理程序
源码:
// 绑定事件的方法
// 源码 5812 行
jQuery.fn.extend( {
// 在被选元素及子元素上添加一个或多个事件处理程序
//$().on('click',function()=<{})
// 源码 5817 行
on: function(types, selector, data, fn) {return on( this, types, selector, data, fn);
},
//xxx
//xxx
})
最终调用的是 jQuery.on() 方法:
// 绑定事件的 on 方法
// 源码 5143 行
// 目标元素,类型(click,mouseenter,focusin,xxx), 回调函数 function(){xxx}
function on(elem, types, selector, data, fn, one) {
var origFn, type;
// 这边可以不看
// Types can be a map of types/handlers
if (typeof types === "object") {// ( types-Object, selector, data)
if (typeof selector !== "string") {// ( types-Object, data)
data = data || selector;
selector = undefined;
}
for (type in types) {on( elem, type, selector, data, types[ type], one );
}
return elem;
}
// 直接调用 $().on()的话会走这边
if (data == null && fn == null) {// ( types, fn)
//fn 赋值为 selector,即 function(){}
fn = selector;
// 再将 selector 置为 undefined
// 注意这个写法,连等赋值
data = selector = undefined;
}
// 调用像 $().click()的话会走这边
else if (fn == null) {if ( typeof selector === "string") {// ( types, selector, fn)
fn = data;
data = undefined;
} else {// ( types, data, fn)
fn = data;
data = selector;
selector = undefined;
}
}
if (fn === false) {fn = returnFalse;} else if (!fn) {return elem;}
//one()走这里
if (one === 1) {
// 将 fn 赋给 origFn 后,再定义 fn
origFn = fn;
fn = function(event) {
// 将绑定给目标元素的事件传给 fn,// 并通过 $().off()卸载掉
// Can use an empty set, since event contains the info
jQuery().off( event);
// 在 origFn 运行一次的基础上,让 origFn 调用 fn 方法,arguments 即 event
return origFn.apply(this, arguments);
};
// 让 fn 和 origFn 使用相同的 guid,这样就能移除 origFn 方法
// Use same guid so caller can remove using origFn
fn.guid = origFn.guid || (origFn.guid = jQuery.guid++);
}
return elem.each(function() {
// 最终调动 $.event.add 方法
jQuery.event.add(this, types, fn, data, selector);
} );
}
解析:
可以看到,由于将 bind()、live() 和 delegate() 都合并进 on() 后,on() 里面的情况挺复杂的,data、selector、fn 相互赋值。
注意下 if (one === 1)
这种情况,是 $().one()
在 on()
里的具体实现,即调用一次 on()
后,就执行jQuery().off( event)
,卸载事件。
该方法最终调用 jQuery.event.add() 方法
四、jQuery.event.add()
作用:
为目标元素添加事件
源码:
// 源码 5235 行
/*
* Helper functions for managing events -- not part of the public interface.
* Props to Dean Edwards' addEvent library for many of the ideas.
*/
jQuery.event = {global: {},
// 源码 5241 行
//this, types, fn, data, selector
add: function(elem, types, handler, data, selector) {
var handleObjIn, eventHandle, tmp,
events, t, handleObj,
special, handlers, type, namespaces, origType,
//elemData 正是目标元素 jQuery 中的 id 属性
// 初始值是{}
elemData = dataPriv.get(elem);
// Don't attach events to noData or text/comment nodes (but allow plain objects)
if (!elemData) {return;}
// 调用者可以传入一个自定义数据对象来代替处理程序
// Caller can pass in an object of custom data in lieu of the handler
if (handler.handler) {
handleObjIn = handler;
handler = handleObjIn.handler;
selector = handleObjIn.selector;
}
// 确保不正确的选择器会抛出异常
// Ensure that invalid selectors throw exceptions at attach time
// Evaluate against documentElement in case elem is a non-element node (e.g., document)
if (selector) {jQuery.find.matchesSelector( documentElement, selector);
}
// 确保 handler 有唯一的 id
// Make sure that the handler has a unique ID, used to find/remove it later
if (!handler.guid) {handler.guid = jQuery.guid++;}
// 如果事件处理没有,则置为空对象
// Init the element's event structure and main handler, if this is the first
// 在这里,就应经给 events 赋值了,// 注意这种写法: 赋值的同时,判断
if (!( events = elemData.events) ) {events = elemData.events = {};
}
if (!( eventHandle = elemData.handle) ) {eventHandle = elemData.handle = function( e) {// 当在一个页面卸载后调用事件时,取消 jQuery.event.trigger()的第二个事件
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
//jQuery.event.triggered: undefined
//e.type: click/mouseout
return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
// 让 elem 调用 jQuery.event.dispatch 方法,参数是 arguments
jQuery.event.dispatch.apply(elem, arguments) : undefined;
};
}
// 通过空格将多个 events 分开,一般为一个,如 click
// Handle multiple events separated by a space
types = (types || "").match(rnothtmlwhite) || [""];
t = types.length;
while (t--) {tmp = rtypenamespace.exec( types[ t] ) || [];
//click
type = origType = tmp[1];
//""namespaces = (tmp[ 2] ||"" ).split(".").sort();
// There *must* be a type, no attaching namespace-only handlers
if (!type) {continue;}
// 如果 event 改变了它自己的 type,就使用特殊的 event handlers
// If event changes its type, use the special event handlers for the changed type
special = jQuery.event.special[type] || {};
// 如果选择器已定义,确定一个特殊 event api 的 type
// 否则使用默认 type
// If selector defined, determine special event api type, otherwise given type
type = (selector ? special.delegateType : special.bindType) || type;
// 不明白为什么在上面要先写一遍
// Update special based on newly reset type
special = jQuery.event.special[type] || {};
//handleObj 会传递给所有的 event handlers
// handleObj is passed to all event handlers
handleObj = jQuery.extend( {
type: type,
origType: origType,
data: data,
handler: handler,
guid: handler.guid,
selector: selector,
needsContext: selector && jQuery.expr.match.needsContext.test(selector),
namespace: namespaces.join(".")
}, handleObjIn );
// 第一次绑定事件,走这里
// Init the event handler queue if we're the first
if (!( handlers = events[ type] ) ) {handlers = events[ type] = [];
handlers.delegateCount = 0;
// Only use addEventListener if the special events handler returns false
if ( !special.setup ||
special.setup.call(elem, data, namespaces, eventHandle) === false ) {
// 目标元素有 addEventListener 的话,调用绑定 click 事件
if (elem.addEventListener) {elem.addEventListener( type, eventHandle);
}
}
}
//special 的 add/handleObj.handler.guidd 的初始化处理
if (special.add) {special.add.call( elem, handleObj);
if (!handleObj.handler.guid) {handleObj.handler.guid = handler.guid;}
}
// Add to the element's handler list, delegates in front
if (selector) {handlers.splice( handlers.delegateCount++, 0, handleObj);
} else {handlers.push( handleObj);
}
// 一旦有绑定事件,全局通知
// Keep track of which events have ever been used, for event optimization
jQuery.event.global[type] = true;
}
},
...
...
}
解析:
可以看到,很多的 if 判断,都是在初始化对象,最后通过 while 循环,调用目标元素的 addEventListener 事件,也就是说,click()/on() 的本质是 element.addEventListener() 事件,前面一系列的铺垫,都是在为目标 jQuery 对象添加必要的属性。
注意写法 if (!( events = elemData.events) )
,在赋值的同时,判断条件
(1)dataPriv
// 取唯一 id
// 源码 4361 行
var dataPriv = new Data();
在 jQuery 对象中,有唯一 id 的属性
$("#one")
elemData = dataPriv.get(elem)
① Data()
// 目标元素的 jQuery id
// 源码 4209 行
function Data() {this.expando = jQuery.expando + Data.uid++;}
② jQuery.expando
jQuery.extend( {
// 相当于 jQuery 为每一个元素取唯一的 id
///\D/g : 去掉非数字的字符
// Unique for each copy of jQuery on the page
// 源码 360 行
expando: "jQuery" + (version + Math.random() ).replace(/\D/g, ""),
...
...
})
③ Math.random()
伪随机,到小数点后 16 位
expando: "jQuery" + (version + Math.random() ).replace(/\D/g, ""),
可以看到 jQuery 的 id 是由 jQuery + 版本号 + Math.random() 生成的
关于 Math.random() 是如何生成伪随机数的请看:https://www.zhihu.com/question/22818104
(2)rtypenamespace
var
rkeyEvent = /^key/,
rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
// 事件类型的命名空间
// 举例:var arr1 = "click.aaa.bbb".match(rtypenamespace);
//console.log(arr1);//["click.aaa.bbb", "click", "aaa.bbb", index: 0, input: "click.aaa.bbb"]
// 源码 5131 行
rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
综上,绑定事件的本质即调用 element.addEventListener()
方法,但 jQuery 有太多的情况需要考虑了。
(完)