jQuery之事件绑定到触发全过程及知识点补充

51次阅读

共计 4650 个字符,预计需要花费 12 分钟才能阅读完成。

前言:
最重要的还是最后的流程图,可以试着根据流程图手写实现$().on(),下篇文章会放出模拟实现的代码。

一、举例

<div id="A" style="background-color: deeppink">
  这是 A

    <div id="C" style="background-color: aqua">
    这是 C
    </div>

</div>

  $("#A").on("click" ,function (event) {console.log(event,"A 被点击了")
  })

  $("#A").on("click" ,"#C",function (event) {console.log(event,"点击了 C,即 C 委托 A 的 click 事件被点击了")
  })

二、$().on()
(1)进行参数的调整
(2)调用jQuery.event.add() 方法

三、jQuery.event.add()最终调用 elem.addEventListener() 来绑定事件
注意:
(1)绑定常用的事件(如:click、focus),使用 handleObj 保存

handleObj = jQuery.extend( {
          //click,mouseout...
          type: type,
          //click,mouseout...
          origType: origType,
          data: data,
          // 事件处理函数, 如 function(){console.log('aaaa')}
          handler: handler,
          // 索引,用于关联元素和事件
          guid: handler.guid,
          // 事件委托的标志,也是委托的对象选择器
          selector: selector,
          needsContext: selector && jQuery.expr.match.needsContext.test(selector),
          // 命名空间,同一 click 事件有两个事件处理程序 handler 的话,// 用这个标识,方便删除或添加 handler
          namespace: namespaces.join(".")
        }, handleObjIn );

(2)如果绑定的是自定义事件(如:windowResize),则使用 handleObjIn 保存

if (handler.handler) {
   handleObjIn = handler;
   handler = handleObjIn.handler;
   selector = handleObjIn.selector;
}

(1)、(2)都会初始化事件处理器(addEventListener):

        // 第一次绑定事件,走这里
        // 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 事件
            //eventHandle 就绑定到 addEventListener 上
            if (elem.addEventListener) {elem.addEventListener( type, eventHandle);
            }
          }
        }

四、jQuery 的事件绑定为何不直接绑定在目标元素身上,而是元素和事件分离?
打印$("#A")

console.log($("#A"),'aaaaaa46')

不要在意 jQueryId 不同的问题,每次刷新网页它都会变化

可以看到

jQuery 的事件和触发事件的 handler 是分离的,
事件集合 存在 事件缓存 dataPrivevents上,

// 获取数据缓存
elemData = dataPriv.get(elem);

而 handler 是由 jQuery.event.dispatch() 处理

elemData.handle = function(e) {jQuery.event.dispatch.apply( elem, arguments)
}

为什么要分离?因为元素如果绑定 click 事件一百次,很耗内存。所以需要将这一百个同类型的事件保存到一个 click 事件集合中,然后在这一大个 click 事件集合内,根据 guid 来执行某一次的 click 处理代码

同一事件的处理:

$('body').on('click', '#one', function(e) {show('委托到 one 触发')
})
$('body').on('click', '#two', function(e) {show('委托到 two 触发')
})

events 是 jQuery 内部的事件队列
handle 是真正绑定到 element 上的事件处理函数

body:{
  events:{
    click:[
      0:{
        guid: 1,
        data: undefined,
        namespace: "",
        origType: "click",
        // 事件委托的标志
        selector: "#one",
        type: "click",
        handler: function(){xxx},
        }
      1:{
        guid: 2,
        data: undefined,
        namespace: "",
        origType: "click",
        // 事件委托的标志
        selector: "#two",
        type: "click",
        handler: function(){xxx},
        }
    ]
  },
  handle: function(){jQuery.event.dispatch.apply( elem, arguments)
  }
}

可以看到,针对同一类型的事件(如 click),重复绑定不会再创建新的内存(new Object 会有新内存),而是在 events 里添加新的绑定事件。

记得看第十一点!

五、guid的作用?
添加 guid 的目的是因为 handler 没有直接跟元素节点发生关联,所以需要一个索引来寻找或者删除handler

六、命名空间 namespace 的作用?

 $("#one").on("click.one",function () {console.log("one 被点击了")
 })
 $("#one").on("click.two",function () {console.log("two 被点击了")
 })

命名空间为:

#one:{
  events:{
    click:[
      0:{
        namespace: "one",
        handler: function(){console.log("one 被点击了")},
        }
      1:{
        namespace: "two",
        handler: function(){xxx},
        }
    ]
  },
}

利用命名空间删除事件:

$("#one").off("click.one")

七、jQuery.event.special 的处理机制
绑定的事件,有些是不能统一处理的,比如 load 事件,是不支持冒泡的,所以即使开发者未用event.stopPropagation,jQuery 也要阻止其冒泡:

jQuery.event ={
  special: {
    load: {
        // Prevent triggered image.load events from bubbling to window.load
        // 阻止冒泡
        noBubble: true
    },
    focus: {
        // Fire native event if possible so blur/focus sequence is correct
        trigger: function() {},
        delegateType: "focusin"
    },
  }
}

八、外部是 Event,内部是数据缓存 events,两者是不一样的
外部 Event:

$().on("click","#B",function(event){console.log("A 被点击了")
})

//click 的 event 就是 jQuery.Event
jQuery.Event{
  handleObj{
    data:undefined,
    guid: 2,
    handler:function(){console.log("A 被点击了")},namespace: "clickA",
    origType: "click",
    selector: "#B",
    type: "click.clickA",
  },
  originalEvent:{// 就是 MouseEvent},
  target:div#B,
  type: "click",
  delegateTarget: div#A,
  //fix 的标志
  jQuery331087940272164138: true,
  currentTarget: div#A,
  isDefaultPrevented:xxx,
  timeStamp:Date.now(),
  isDefaultPrevented:function(){return false}
}

内部缓存 events:

let events = dataPriv.get(this, "events")

events[
    delegantCount:1,
   {
      data:undefined,
      guid: 2,
      handler:function(){console.log("B 委托 A 被点击了")},namespace: "clickA",
      origType: "click",
      selector: "#B",
      type: "click.clickA",
    },
   {
     data:undefined,
      guid: 1,
      handler:function(){console.log("A 被点击了")},namespace: "",
      origType: "click",
      selector: undefined,
      type: "click",
    }   


]

九、为什么要使用 fix() 来重构 js 的原生 MouseEvent 对象呢?
(1)jQuery 有自己的一套 event 处理机制,所以需要符合 jQueryevent对象
(2)可以传递 data 数据,即用户自定义的数据。

十、trigger 的触发机制

  $("#A").on("click" ,function (event) {console.log(event,"A 被点击了")
  })

元素 #A 本身绑定了一个 click 事件,但是 click 是原生事件,它是靠 addEventListener绑定来触发事件的。

但是!jQuerytrigger 是能够无差别模拟这个交互行为的

$("#A").trigger("click")

trigger() 的功能上就可以解释 为什么 jQuery 要设计元素与数据分离 了:

如果是直接绑定的话就无法通过 trigger 的机制去触发 click 事件,
正是因为 jQuery 没有直接把事件相关的 handler 与元素直接绑定,而是采用了分离处理,
所以我们通过 trigger 触发 click 事件与 addEventListener 触发 click 事件的处理流程是 一致 的,不同的只是触发的方式而已。

但是,通 trigger 触发的事件是没有事件对象 (event)、冒泡(bubble) 这些特性的,所以我们需要有一个功能 能模拟出事件对象,然后生成一个遍历树 (eventPath) 模拟出冒泡行为,这个就交给了 trigger 方法了

关于 $().trigger() 的源码解析请看:jQuery 源码解析之 trigger()


最后,附上自己做的 jQuery事件绑定到触发全过程的流程图:


(完)

正文完
 0