【源码小记】jQueryの事件绑定
前言
这篇笔记很大的帮助来源于慕课网上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的事件机制,比较复杂,所以另再开坑来学习。