jQuery 1.9.1源码分析系列(十)事件系统之绑定事件


Posted in Javascript onNovember 19, 2015

事件绑定的方法有很多种,使用了jquery那么原理那种绑定方式(elem.click = function(){...}))就不太想推荐给大家了。最主要的原因是elem.click=fn这种方式只能绑定一个事件处理,多次绑定的只会保留最后一次绑定的结果。

下面给大家介绍jquery绑定事件的方式有哪些吧。

jQuery.fn.eventType([[data,] fn])

比如eventType指的是事件类型,比如click: $("#chua").click(fn);

data这个参数一般都不会使用。这种方式事件绑定在("#chua")上,没有委托事件,和js原生的事件绑定更接近。我们看一下源码

jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
  "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
  "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {


//合并15种事件统一增加到jQuery.fn上,内部调用this.on / this.trigger



jQuery.fn[ name ] = function( data, fn ) {



return arguments.length > 0 ?



this.on( name, null, data, fn ) :



//如果不带参数表示立刻触发指定事件



this.trigger( name );
 
};
});
jQuery.fn.bind( types[, data], fn )

比如$("#chua").bind("click",fn)。直接将事件绑定到$("#chua")上,没有委托事件。源码

bind: function( types, data, fn ) {
 return this.on( types, null, data, fn );
},
unbind: function( types, fn ) {
 return this.off( types, null, fn );
}
 jQuery.fn.delegate(selector, types[, data], fn)

顾名思义delegate这个函数是用来做事件委托的,将选择器selector对应的响应处理委托给当前jQuery所匹配的元素。

比如:$(document).delegate('#big',"click",dohander);分析到这里顺便分析一下事件委托的处理流程。

当点击"#big"元素的时候,事件click会冒泡直到document节点;

document绑定了处理事件,这个处理事件会调用到事件分发器dispatch;

dispatch中取出对应事件类型click的所有的委托事件列表handlers;

根据事件源event.target过滤出委托事件列表handlers中每一个元素的selector属性对应的节点处于事件原和委托节点document之间(包括事件源)的委托事件,保存为handlerQueue;

执行handlerQueue里面的事件处理。

上面是一个大致的流程,后续会详细分析。先看delegate源码

delegate: function( selector, types, data, fn ) {
 return this.on( types, selector, data, fn );
},
undelegate: function( selector, types, fn ) {
 // ( namespace ) or ( selector, types [, fn] )
 return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
}
jQuery.fn.one( types[, selector[, data]], fn )

通过one()函数绑定的事件处理函数都是一次性的,只有首次触发事件时会执行该事件处理函数。触发之后,jQuery就会移除当前事件绑定。

比如$("#chua").one("click",fn);为#chua节点绑定一次性的click事件

$(document).one("click","#chua",fn);将#chua的click事件委托给document处理。源码

one: function( types, selector, data, fn ) {
  return this.on( types, selector, data, fn, 1 );
} 
jQuery.fn.trigger(type[, data])
jQuery.fn.triggerHandler(type[, data])

trigger触发jQuery对象所匹配的每一个元素对应type类型的事件。比如$("#chua").trigger("click");

triggeHandler只触发jQuery对象所匹配的元素中的第一个元素对应的type类型的事件,且不会触发事件的默认行为。

//立刻触发jQuery对象内所有元素的指定type的事件
trigger: function( type, data ) {
 return this.each(function() {
  jQuery.event.trigger( type, data, this );
 });
},
//立刻触发jQuery对象内第一个元素的指定type的事件,且不会触发事件(比如表单提交)的默认行为
triggerHandler: function( type, data ) {
 var elem = this[0];
 if ( elem ) {
  return jQuery.event.trigger( type, data, elem, true );
 }
}

上面分析了那么些个事件绑定,有么有发现他们都是使用.on方式绑定的?这也是为什么提倡统一使用on来绑定的原因(one方式除外)。

jQuery.fn.on( types[, selector[, data]], fn )

.on的事件绑定一半的代码都实在处理传递不同参数的处理,这也是jQuery的口号Write less, do more的代价吧。最终使用jQuery.event.add来绑定事件。

  jQuery.event.add绑定事件有几个比较关键的地方:

第一个,使用内部缓存来保存节点elem的事件信息

//获取缓存数据 


 elemData = jQuery._data( elem );



 ...



 



 //设置缓存数据
   if ( !(events = elemData.events) ) {
    events = elemData.events = {};
   }
   if ( !(eventHandle = elemData.handle) ) {
    eventHandle = elemData.handle = function( e ) {
     ...
    };
    //将elem作为handle函数的一个特征防止ie非本地事件引起的内存泄露
    eventHandle.elem = elem;
   }

第二个,设置绑定事件信息,特别是指定的选择器selector、响应处理handler、响应事件类型type、命名空间namespace

// handleObj:设置绑定事件信息。贯穿整个事件处理
  handleObj = jQuery.extend({
   type: type,
   origType: origType,
   data: data,
   handler: handler,
   guid: handler.guid,
   selector: selector,
   // For use in libraries implementing .is(). We use this for POS matching in `select`
   //"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
   //whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
   //用来判断亲密关系
   needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
   namespace: namespaces.join(".")
  }, handleObjIn );

第三个,节点的事件列表中,真正的委托事件列表放置在前面,和delegateCount属性同步,即events.click.length假设为3,events.click.delegateCount假设为2。那么events.click[0]和events.click[1]所指定事件是委托事件。第三个events.click[2]对应的事件不是委托事件,而是节点自身的事件。

//将事件对象handleObj添加到元素的处理列表,委托事件放在前面,委托代理计数递增
  if ( selector ) {
   handlers.splice( handlers.delegateCount++, 0, handleObj );
  } else {
   handlers.push( handleObj );
  }

源码和添加事件后的结构上一章已经分析,详情请点击查看

绑定有一个公用函数jQuery.fn.on。解绑同样有一个公用函数jQuery.fn.off

jQuery.fn.off([ types[, selector][, fn]] )

这里的传参有个比较特殊的情况:当types是浏览器事件对象event的时候,表示要去掉(解绑)委托节点上event.selector指定的委托事件

//传入的参数是事件且绑定了处理函数
if ( types && types.preventDefault && types.handleObj ) {
  // ( event ) dispatched jQuery.Event
  handleObj = types.handleObj;
  //types.delegateTarget是事件托管对象
  jQuery( types.delegateTarget ).off(
   //组合jQuery识别的type
   handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
   handleObj.selector,
   handleObj.handler
   );
  return this;
}

无论如何最终都是调用jQuery.event.remove函数来解绑事件。

jQuery.fn.off完整的源码如下

off: function( types, selector, fn ) {
var handleObj, type;

//传入的参数是事件且绑定了处理函数

if ( types && types.preventDefault && types.handleObj ) {
  // ( event ) dispatched jQuery.Event
  handleObj = types.handleObj;
  //types.delegateTarget是事件托管对象
  jQuery( types.delegateTarget ).off(
   //组合jQuery识别的type
   handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
   handleObj.selector,
   handleObj.handler
   );
  return this;

}

if ( typeof types === "object" ) {
  // ( types-object [, selector] )
  for ( type in types ) {
   this.off( type, selector, types[ type ] );
  }
  return this;

}

if ( selector === false || typeof selector === "function" ) {
  // ( types [, fn] )
  fn = selector;
  selector = undefined;

}

if ( fn === false ) {
  fn = returnFalse;

}

return this.each(function() {
  jQuery.event.remove( this, types, fn, selector );

});
}

接下来分析一下事件解绑的低级api jQuery.event.remove。

jQuery.event.remove

jQuery使用.off()函数伤处绑定的事件时内部调用的基础函数是jQuery.event.remove。该函数的处理流程如下

1. 分解传入的要删除的事件类型types,遍历类型,如果要删除的事件没有事件名,只有命名空间则表示删除该命名空间下所有绑定事件

//分解types为type.namespace为单位元素的数组
types = ( types || "" ).match( core_rnotwhite ) || [""];
t = types.length;
while ( t-- ) {
   tmp = rtypenamespace.exec( types[t] ) || [];
   type = origType = tmp[1];
   namespaces = ( tmp[2] || "" ).split( "." ).sort();

   //解绑当前元素的当前命名空间(types[ t ])上所有的事件
   if ( !type ) {
    for ( type in events ) {
     jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
    }
    continue;
   }
   ...

2. 遍历类型过程中,删除匹配的事件,代理计数修正

type = ( selector ? special.delegateType : special.bindType ) || type;
handlers = events[ type ] || [];
tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
//删除匹配事件
origCount = j = handlers.length;
while ( j-- ) {
   handleObj = handlers[ j ];

   //各种满足移除事件的条件才能移除
   if ( ( mappedTypes || origType === handleObj.origType ) &&
    ( !handler || handler.guid === handleObj.guid ) &&
    ( !tmp || tmp.test( handleObj.namespace ) ) &&
    ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
    handlers.splice( j, 1 ); 



if ( handleObj.selector ) {





handlers.delegateCount--;




}




if ( special.remove ) {





special.remove.call( elem, handleObj );




}
   }
}

3. 如果节点上指定类型的事件处理器已经为空,则将events上的该类型的事件处理对象移除

// 移除事件处理对象
// (移除特殊事件处理过程中避免潜在的无限递归,下一章会专门详解这种情况)
if ( origCount && !handlers.length ) {
//例如 var js_obj = document.createElement("div"); js_obj.onclick = function(){ …}
//上面的js_obj是一个DOM元素的引用,DOM元素它长期在网页当中,不会消失,而这个DOM元素的一属性onclick,又是内部的函数引用(闭包),而这个匿名函数又和js_obj之间有隐藏的关联(作用域链)所以形成了一个,循环引用.
if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
jQuery.removeEvent( elem, type, elemData.handle );
}

delete events[ type ];
}

4. 如果节点上没有任何绑定的事件,则清空事件处理入口handle

if ( jQuery.isEmptyObject( events ) ) {
delete elemData.handle;

//removeData还检事件对象是否为空,所以使用它替代delete

jQuery._removeData( elem, "events" );
}

拓展: 浏览器事件删除jQuery.removeEvent

jQuery.removeEvent = document.removeEventListener ?
function( elem, type, handle ) {
   if ( elem.removeEventListener ) {
    elem.removeEventListener( type, handle, false );
   }
} :
function( elem, type, handle ) {
   var name = "on" + type;
   if ( elem.detachEvent ) {
    // #8545, #7054,避免自定义事件在IE6-8中的内存泄露
    // detachEvent需要传递第一个参数,不能是undefined的
    if ( typeof elem[ name ] === core_strundefined ) {
     elem[ name ] = null;
    }
    elem.detachEvent( name, handle );
   }
};

以上内容是小编给大家介绍的jQuery 1.9.1源码分析系列(十)事件系统之绑定事件,希望大家喜欢。

Javascript 相关文章推荐
xml 封装与解析(javascript和C#中)
Jul 26 Javascript
分享XmlHttpRequest调用Webservice的一点心得
Jul 20 Javascript
一个简单的jquery进度条示例
Apr 28 Javascript
jquery的ajax异步请求接收返回json数据实例
Jun 16 Javascript
AngularJs验证重复密码的方法(两种)
Nov 25 Javascript
基于Angular.js实现的触摸滑动动画实例代码
Feb 19 Javascript
vue的Virtual Dom实现snabbdom解密
May 03 Javascript
Angular中支持SCSS的方法
Nov 18 Javascript
Webpack之tree-starking 解析
Sep 11 Javascript
微信小程序生成分享海报方法(附带二维码生成)
Mar 29 Javascript
JSONP 的原理、理解 与 实例分析
May 16 Javascript
Node快速切换版本、版本回退(降级)、版本更新(升级)
Jan 07 Javascript
基于Jquery代码实现手风琴菜单
Nov 19 #Javascript
跟我学习javascript的作用域与作用域链
Nov 19 #Javascript
每天一篇javascript学习小结(属性定义方法)
Nov 19 #Javascript
理解 JavaScript Scoping & Hoisting(二)
Nov 18 #Javascript
js立即执行函数: (function ( ){})( ) 与 (function ( ){}( )) 有什么区别?
Nov 18 #Javascript
z-blog SyntaxHighlighter 长代码无法换行解决办法(基于jquery)
Nov 18 #Javascript
JavaScript如何获取数组最大值和最小值
Nov 18 #Javascript
You might like
php 图像函数大举例(非原创)
2009/06/20 PHP
PHP导入导出Excel代码
2015/07/07 PHP
PHP QRCODE生成彩色二维码的方法
2016/05/19 PHP
PHP基于面向对象实现的留言本功能实例
2018/04/04 PHP
建议大家看下JavaScript重要知识更新
2007/07/08 Javascript
JavaScript限定复选框的选择个数示例代码
2013/08/25 Javascript
JS+CSS实现简易实用的滑动门菜单效果
2015/09/18 Javascript
js复制内容到剪贴板代码,js复制代码的简单实例
2016/10/27 Javascript
详解如何用webpack打包一个网站应用项目
2017/07/12 Javascript
angularjs中$http异步上传Excel文件方法
2018/02/23 Javascript
Angular 封装并发布组件的方法示例
2018/04/19 Javascript
JavaScript判断数组类型的方法
2019/10/23 Javascript
微信小程序自定义弹出模态框禁止底部滚动功能
2020/03/09 Javascript
JavaScript面向对象核心知识与概念归纳整理
2020/05/09 Javascript
js实现无刷新监听URL的变化示例代码详解
2020/06/03 Javascript
JavaScript Event Loop相关原理解析
2020/06/10 Javascript
vue.js实现点击图标放大离开时缩小的代码
2021/01/27 Vue.js
[03:48]显微镜下的DOTA2第四期——TP动作
2014/06/20 DOTA
Python寻找两个有序数组的中位数实例详解
2018/12/05 Python
Python面向对象程序设计之私有属性及私有方法示例
2019/04/08 Python
对python 调用类属性的方法详解
2019/07/02 Python
Python-Seaborn热图绘制的实现方法
2019/07/15 Python
python从内存地址上加载python对象过程详解
2020/01/08 Python
Django Model层F,Q对象和聚合函数原理解析
2020/11/12 Python
css3实现波纹特效、H5实现动态波浪效果
2018/01/31 HTML / CSS
台湾东南旅游社网站:东南旅游
2019/02/11 全球购物
白俄罗斯女装和针织品网上商店:Presli.by
2019/10/13 全球购物
英国排名第一的冲浪店:Ann’s Cottage
2020/06/21 全球购物
Swanson中国官网:美国斯旺森健康产品公司
2021/03/01 全球购物
链表面试题-一个链表的结点结构
2015/05/04 面试题
学校采购员岗位职责
2014/01/02 职场文书
机电一体化应届生求职信范文
2014/01/24 职场文书
党员自我评议对照检查材料
2014/09/27 职场文书
合理化建议书范文
2015/09/14 职场文书
丧事答谢词大全
2015/09/30 职场文书
sql注入报错之注入原理实例解析
2022/06/10 MySQL