5.分析JQuery源码的事件系统

事件系统

以jquery的版本v3.4.1为例来进行分析.我们首先看下jquery事件绑定常用的方法如下:

 绑定原生事件
 $("#kkk").on("click",function(){}) 

 //自定义事件
 $("#kkk").on("xxx",(e,x)=>{
        e.stopPropagation();
        console.log(123+x);
 })

 //触发自定义事件
 $("#kkk").trigger("xxx",1212);

 //触发原生事件
 $("#input").trigger("focus");   
    

如上所写,on既可以绑定原生事件,也可以绑定自定义事件.而trigger既可以触发原生事件,也可以触发自定义事件,接下来我们先看下一下on的源码实现,在追踪on函数实现之前先了解一下jQuery的Data缓存机制

 

Data缓存

Data是一个构造函数(代码如下),里面有一个属性expando,可以看做成为一个随机生成的字符串作为id的标识.jQuery.expando是一个常量,如果一旦new了一个Data实例那么该实例对象的expando属性就是唯一并且确定的.

在Data的原型上主要有三个方法,分别是cache,add和set方法.我们先看cache方法,owner我们看做成传递过来的dom对象,第一次执行cache函数时dom对象上肯定没有expando属性.没有的话它就会创建一个空对象value,并把expando作为key,value作为值绑定到dom对象上,如图所示.第二次执行该函数就会直接返回value的值.

   

set函数呢就是获取到上面所说的value这个对象,然后呢给这个对象设置key和value.

get函数通过传入的key值寻找到value这个对象上对应的值.

从这三个实现方法我可以看出来value这个数据对象它并不是存储在Data构造的实例当中而是存储在dom对象上.而cache,set和get函数也只是对dom对象上挂载的vaue对象进行赋值和获取值之类的操作.

function Data() {
	this.expando = jQuery.expando + Data.uid++;
}

Data.uid = 1;

Data.prototype = {
	cache: function( owner ) {

		// Check if the owner object already has a cache
		var value = owner[ this.expando ];

		// If not, create one
		if ( !value ) {
			value = {};

			if ( acceptData( owner ) ) {

				if ( owner.nodeType ) {
					owner[ this.expando ] = value;
				} else {
					Object.defineProperty( owner, this.expando, {
						value: value,
						configurable: true
					} );
				}
			}
		}

		return value;
	},
	set: function( owner, data, value ) {
		var prop,
			cache = this.cache( owner );

		// Handle: [ owner, key, value ] args
		// Always use camelCase key (gh-2257)
		if ( typeof data === "string" ) {
			cache[ camelCase( data ) ] = value;

		// Handle: [ owner, { properties } ] args
		} else {

			// Copy the properties one-by-one to the cache object
			for ( prop in data ) {
				cache[ camelCase( prop ) ] = data[ prop ];
			}
		}
		return cache;
	},
	get: function( owner, key ) {
		return key === undefined ?
			this.cache( owner ) :
			owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
	}
}

 

 

on函数实现

先用浏览器调试工具追踪一下on函数的执行过程.

 

jQuery.event.add函数实现

1.这是执行的第一个函数,继续往下看return旁边的on函数实现

 

2.在on函数核心的代码是这一句,elem.each是给dom元素同时绑定多个事件,如果用户只用on绑定了一个事件,就只执行一次jQuery.event.add函数了,由此可以看出给元素绑定事件的逻辑全部写在add函数里了.

 

3.jQuery.event.add函数内的代码量有点多,定义的变量和函数错综复杂第一次看的时候难免会一头雾水,我们现在把焦点投射在核心代码上去一步步窥探它的实现过程.

elem不用多说自然是要绑定事件的主体dom对象.types呢它是绑定的事件类型,此时它会存在两种情况,它可能是原生事件比如click,focus,也可能是用户自定义的事件比如xxx.handler就是用户编写的触发事件后的回调函数.

在这几行代码里我们需要重点关注第5009行,dataPriv是个什么东西.看到没它是Data构造函数的一个实例.并且调用了get方法,由上面的Data缓存机制的描述,又因为是第一次执行不难得出dataPriv就是elem上面挂载的value对象并且还是一个空对象.

这个add函数经过一系列的数据运算操作最后无非是做两件事情,第一个是给dom元素绑定一个事件,第二个给dom对象上的value对象赋值.

给dom元素绑定一个事件,type是事件类型也是用户传入的.但是eventHanlder并不是用户传递的回调函数,而是jQuery.event.dispatch这个函数.

 

执行完add函数后看下给dom对象挂载的value对象变成了什么样子.它有两个属性handle和events.其中handler指向了上面提到的jQuery.event.dispatch函数.events里面有个click事件类型,说明用户使用on绑定了一个点击事件.里面有type,handler和guid.此处要格外注意这个handler才是用户填写的回调函数,而jQuery.event.dispatch函数里面做的事情就是想办法去执行这个回调函数.

 

jQuery.event.dispatch函数实现

 

dispatch函数首先是用原生的事件类型和用户传入的数据组合成一个数组args.随后提取dom对象上面缓存的value对象的handler函数,也就是用户编写的回调函数进行执行,并将args作为参数传入.

 

以上便是on函数的核心实现流程,jquery并没有在add函数里直接绑定handler函数而是绑定了一个dispatch函数,在disptach里面再去执行这个handler.它为什么要这样绕一层去执行函数呢,因为如此设置便可以轻松的实现自定义事件的绑定和触发了.

 

 

自定义事件触发函数trigger

$.trigger的实现,它有两种情况:

第一种是触发自定义事件:$.trigger("xxxx",123);第二个情况触发dom元素的事件:$("input").focus();

1.trigger函数首先会先生成一个自定义的event事件对象,并且把元素赋值上去:event.target = elem;随后呢它会把事件对象和用户传递的参数组合成一个数组data

 data = jQuery.makeArray( data, [ event ] );

2.接下来就会出现一个分水岭:

special = jQuery.event.special[ type ] || {};
if (special.trigger.apply( elem, data ) === false ) {
   return;
}

如果用户触发的是原生事件,special里面是jQuery全局定义了一些原生事件比如:click,focus,blur.special里面会含有相关的处理函数,此种情况下就会执行special.trigger函数,这个函数的作用只有一个,就是把上面说的value对象的绑定的回调函数handler指向special.trigger里面指定的一个函数,而这个函数里面它的核心代码如下:

 if(this.[type]){

   this.[type](); //如果dom对象有这个事件直接执行了
} 

从这里可以看出来$("#input").trigger("focus")内部执行的其实是input.focus();

 

3.继续走,这里的代码将元素的所有父元素收集起来,模拟冒泡事件

if ( !rfocusMorph.test(  ) ) {
  cur = cur.parentNode;
}
for ( ; cur; cur = cur.parentNode ) {
 eventPath.push( cur );
}

这里模拟冒泡事件,如果用户调用了e.stopPrapogation()在这里!event.isPropagationStopped()就会跳出循环.

while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
  event.type =  type;
  handle = dataPriv.get( cur, "handle" );
  if ( handle ) {
   handle.apply( cur, data );
  }
}

执行到这里最终的结果就是执行handle函数,并且把dom对象和[event,用户传递参数]一起给handle,这个hanlder其实就是在上面add函数里面绑定的.

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

4.它最后又会回到上面提到的disptach函数,disptach函数主要做的事情就是把dom对象上缓存的handler函数(用户定义的回调函数)取出来执行它,在这里有一点区别的是如果用户触发的是原生事件$.trigger("focus"),那么这个handler早已换成special.trigger里面绑定的函数了,这个过程在上面叙述过。如果是自定义事件,这个handler就是用户定义的函数了.至此最终就能执行到用户定义的那个函数了.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值