前言
这篇笔记很大的帮助来源于慕课网上jQuery源码解读课程,虽然jQuery现在已经慢慢衰退了,但是作为一个学习者,我自己还是很愿意去琢磨琢磨其中的原理。如果有错误欢迎指出,谢谢了^0^。
事件绑定
大家肯定都熟悉JavaScript中的浏览器事件模型——事件冒泡和事件捕获。在DOM2级事件处理程序中,我们可以使用addEventListener
方法来添加事件监听,第三个参数可以指定是捕获阶段(true)还是冒泡阶段(false),在IE中对应的方法是attachEvent
,且只支持冒泡事件。
Q:为什么我们要使用addEventListener?
我们以往的写法是XX.onclick=function(){}
,这样不能绑定多个相同事件(会覆盖),也不能选择是在冒泡阶段/捕获阶段。
Q:这样的绑定可能会出现什么问题?
- 绑定的元素必须存在;
- 后期动态生成的HTML元素不会被自动加上绑定;
- 绑定过多会影响性能;
- 语法繁杂;
Q:什么是事件委托?
注意看上面问题的第3点,假设我们的页面上有100个p标签,那我们就要添加100个事件监听,这样会影响页面的运行性能。
但是如果我们给body一个click监听,当用户点击的页面中的p标签,实质上也是点击body,所以body的这个click事件会从target
开始进行层层冒泡,如果target就是我们的目标元素,那么就相应去运行我们想调用的函数,这样功效不也等同于监听了p标签吗?
所以事件委托的定义是:
事件目标自身不处理事件,而是把处理任务委托给其父元素或者祖先元素,甚至根元素(document)
这样一来,就算目标目前不存在它的容器里也不会发生错误,也同样减少了绑定次数。
所以模拟给a加上事件委托代码如下:
(function() {
var parent = document.getElementById('parent');
//给祖先元素绑定监听
parent.addEventListener('click', handler, false);
function handler(e) {
//或许触发冒泡的元素,如果元素是a,就做想做的事情
var x = e.target;
if (x.nodeName.toLowerCase() === 'a') {
alert('a被点击了!');
e.preventDefault();
}
};
})();
jQuery的事件绑定方法
在jQuery中,与事件绑定相关的方法有以下几种:
- 事件名方法(比如click())
- bind()
- delegate()
- on()
live()
Tips:live()在1.9版本已被废除。
而以上的几种方法,bind() ,live() ,delegate()都是调用了on()来实现的,而归根结底都是使用了addEventListener
以及事件冒泡来实现的。
bind()
调用:
$("#test").bind("click",function(){
console.log(1);
});
源码
bind()方法实际直接调用了on()方法,types是事件类型,data是传入的要执行函数,更详细的我们一起放到on()方法去看。但是先说清楚,它没有用委托机制,元素必须要存在。
live()
调用:
$("#test").bind("click",function(){
console.log(1);
});
源码:
live()方法使用了事件委托机制,把所有的委托都交给了this.context(相当于document
)来完成。
delegate()
调用:
$("body").delegate("#test","click",function(){
console.log(1);
})
源码:
这里看到其实delegate和live是类似的,只是live是this.context调用on方法,delegate是自己可以指定委托对象,所以直接this调用on方法
on()
调用:
//直接调用
$("#test").on("click",function(){
console.log(1);
})
//委托调用
$("body").on("click","#test",function(){
console.log(1);
})
源码:
值得一提,one方法也是调用了on方法,不过它是只调用一次事件,随后就解绑。
/* on()方法,参数可能会错位,所以需要处理
* elem: 被委托元素(调用on方法选择元素)
* types:事件类型(我们举例子传入的都是click)
* selector:可选,真正的目标元素
* data: 可选,额外数据
* fn: 可选,监听到后要调用的函数
* one:内部传入,是否是one()方法调用的
*/
function on( elem, types, selector, data, fn, one ) {
var origFn, type;
//传入的types可能是一个数组(可以同时绑定多个事件)
if ( typeof types === "object" ) {
//如果selector不是string,说明传入了types,data
if ( typeof selector !== "string" ) {
//所以这修正一下data和selector的值
data = data || selector;
selector = undefined;
}
//遍历每一个事件,去分别添加绑定
for ( type in types ) {
on( elem, type, selector, data, types[ type ], one );
}
//返回elem,以支持链式调用
return elem;
}
//如果只传了三个参数,说明是传入了types,fn
if ( data == null && fn == null ) {
// 把参数归位
fn = selector;
data = selector = undefined;
} else if ( fn == null ) {
// 如果只有fn是null,且selector是string,说明是传入了types,fn,selector
if ( typeof selector === "string" ) {
//参数归位
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 ) {
//保存要调用的函数
origFn = fn;
fn = function( event ) {
//解绑事件
jQuery().off( event );
//调用函数
return origFn.apply( this, arguments );
};
//给origFn和fn使用同样的记录函数的guid,方便移除
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
//给每个元素注册事件处理程序
return elem.each( function() {
jQuery.event.add( this, types, fn, data, selector );
} );
}
这里的guid和event.add()就涉及到了jQuery的事件机制,比较复杂,所以另再开坑来学习。