【源码小记】jQueryの事件绑定

前言

这篇笔记很大的帮助来源于慕课网上jQuery源码解读课程,虽然jQuery现在已经慢慢衰退了,但是作为一个学习者,我自己还是很愿意去琢磨琢磨其中的原理。如果有错误欢迎指出,谢谢了^0^。

事件绑定

大家肯定都熟悉JavaScript中的浏览器事件模型——事件冒泡和事件捕获。在DOM2级事件处理程序中,我们可以使用addEventListener方法来添加事件监听,第三个参数可以指定是捕获阶段(true)还是冒泡阶段(false),在IE中对应的方法是attachEvent,且只支持冒泡事件。

Q:为什么我们要使用addEventListener?
我们以往的写法是XX.onclick=function(){},这样不能绑定多个相同事件(会覆盖),也不能选择是在冒泡阶段/捕获阶段。

Q:这样的绑定可能会出现什么问题?

  1. 绑定的元素必须存在;
  2. 后期动态生成的HTML元素不会被自动加上绑定;
  3. 绑定过多会影响性能;
  4. 语法繁杂;

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中,与事件绑定相关的方法有以下几种:

  1. 事件名方法(比如click())
  2. bind()
  3. delegate()
  4. on()
  5. live()

Tips:live()在1.9版本已被废除。
而以上的几种方法,bind() ,live() ,delegate()都是调用了on()来实现的,而归根结底都是使用了addEventListener以及事件冒泡来实现的。

bind()

调用:

$("#test").bind("click",function(){
  console.log(1);
});

源码
【源码小记】jQueryの事件绑定

bind()方法实际直接调用了on()方法,types是事件类型,data是传入的要执行函数,更详细的我们一起放到on()方法去看。但是先说清楚,它没有用委托机制元素必须要存在

live()

调用:

$("#test").bind("click",function(){
  console.log(1);
});

源码:
【源码小记】jQueryの事件绑定

live()方法使用了事件委托机制,把所有的委托都交给了this.context(相当于document)来完成。

delegate()

调用:

$("body").delegate("#test","click",function(){
  console.log(1);
 })

源码:
【源码小记】jQueryの事件绑定

这里看到其实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);
 })

源码:
【源码小记】jQueryの事件绑定
值得一提,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的事件机制,比较复杂,所以另再开坑来学习。

相关推荐