jquery绑定原理 简单解析与实现代码分享


Posted in Javascript onSeptember 06, 2011

jq里面有一个data的方法,给dom元素绑定相关的数据的。当给dom用jq的方法绑定了事件,会生成对应的时间列表
可以看下面的例子(请在firefox中查看 因为firefox中对象支持toSource())

<!DOCTYPE html> 
<html> 
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> 
<title></title> 
</head> 
<body> 
<div id="test"></div> 
<script type="text/javascript" src="http://common.cnblogs.com/script/jquery.js"></script> 
<script type="text/javascript"> 
window.onload = function(){ 
alert($.data($('#test')[0],'events'));//null 
alert($.data($('#test')[0],'handle'));//null 
$('#test') 
.bind('click',function(){ 
alert(1) 
}) 
.bind('mouseover',function(){ 
alert(2) 
}) 
.bind('click',function(){ 
alert(3) 
}) 
.bind('click',function(){ 
alert(4) 
}) 
alert($.data($('#test')[0],'events').toSource());//时间列表 
alert($.data($('#test')[0],'handle').toSource());//执行的函数 
} 
</script> 
</body> 
</html>

data是给元素绑定数据的
数据源是 cache对象
当元素绑定数据的时候 会给元素添加一个属性 jQueryxxx xxx为执行jq的时间戳
这里要说明一下,有一个uuid 他是累加的
jQueryxxx的值就是这个uuid
cache 的 key就是这个 uuid
value就是要存的数据
data对于事件的绑定是很重要的................................
function now(){ 
return +new Date; 
}; 
var win = this, 
expando = "jQuery" + now(), 
uuid = 0, 
cache = {}; 
win.data = function(elem, name, data){ 
var id = elem[expando]; 
if(!id) 
id = elem[expando] = ++uuid; 
if(name&&!cache[id]) 
cache[id] = {}; 
if(data !== undefined) 
cache[id][name] = data; 
return name 
? cache[id][name] 
: id; 
} 
win.removeData = function(elem, name){ 
var id = elem[expando]; 
if (name){ 
if (cache[id]) { 
delete cache[id][name]; 
name = ""; 
for ( name in cache[ id ] ) 
break; 
if ( !name ) 
removeData(elem); 
} 
}else{ 
try { 
delete elem[expando]; 
} catch(e){ 
if ( elem.removeAttribute ) 
elem.removeAttribute( expando ); 
} 
delete cache[id]; 
} 
} 
win.each = function( object, callback, args ) { 
var name, i = 0, length = object.length; 
if ( args ) { 
if ( length === undefined ) { 
for ( name in object ) 
if ( callback.apply( object[ name ], args ) === false ) 
break; 
} else 
for ( ; i < length; ) 
if ( callback.apply( object[ i++ ], args ) === false ) 
break; 
} else { 
if ( length === undefined ) { 
for ( name in object ) 
if ( callback.call( object[ name ], name, object[ name ] ) === false ) 
break; 
} else 
for ( var value = object[0]; 
i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} 
} 
return object; 
}

接着实现添加事件
jq里面是在 jQuery.event里面的add方法
在add方法里面实现了一下一些功能
取元素的events,handle这2个data绑定的数据
events存放的是事件列表
格式如下
{
click: [{handler:function(){},type:"click",guid:'xx'}.......],
mouse:[......]
}
handle是执行的函数
(所有的执行函数都是一样的 他们遍历事件列表 执行对应的事件)
然后遍历types 因为可以绑定多个事件
回调函数也会给几个属性
假设回调函数是handler
handler.guid = gevent.guid++
handler.type = name
name应该算一个特殊的命名 方便删除用的
比如
$('#xx')
.bind('click',function(){})
.bind('click.d',handler)
name就是d了
删除的时候可以只删除d那个事件 不删除上面的那个 click事件
最后是给元素绑定事件 但是执行的函数都是
function(){
gevent.handle.apply(arguments.callee.elem, arguments);
});
win.gevent = { 
guid : 1, 
add : function (elem, types, handler){ 
if ( elem.nodeType == 3 || elem.nodeType == 8 ) 
return; 
if ( elem.setInterval && elem != window ) 
elem = window; 
//给函数一个唯一标识的索引 方便后面删除该事件 
if ( !handler.guid ) 
handler.guid = this.guid++; 
//获得该元素的events handle 下的数据 
var events = data(elem, "events") || data(elem, "events", {}), 
handle =data(elem, "handle") || data(elem, "handle", function(){ 
//gevent.handle才是各种行为触发后会执行的函数 
gevent.handle.apply(arguments.callee.elem, arguments); 
}); 
handle.elem = elem; 
//遍历事件名 因为可以是 click mouseover 
each(types.split(/\s+/), function(index, type) { 
var namespaces = type.split("."); 
//获得事件名 
type = namespaces.shift(); 
//去掉点后面的东西 是个特殊的命名 在删除的时候可以指定删除他 如 click.d 
//用事件的type 记录住这个特殊的命名 
handler.type = namespaces.slice().sort().join("."); 
//获得该事件是否已经存在events 这个对象里面了 
var handlers = events[type]; 
//如果不存在该事件 给元素绑定该事件 
if (!handlers) { 
handlers = events[type] = {}; 
if (elem.addEventListener) 
elem.addEventListener(type, handle, false); 
else if (elem.attachEvent) 
elem.attachEvent("on" + type, handle); 
} 
//吧函数放到元素的该事件的列表里面 
handlers[handler.guid] = handler; 
}); 
elem = null; 
} 
}

gevent.hander是绑定事件真正执行的函数
在gevent.hander里面也有取.特殊命名的地方 但是不知道做什么用的
hander里面先对event进行包装
包装见gevent. fix 和 setEvent
主要是对做一个原生event的一个copy 然后把不兼容的方法 都合成兼容的写法
然后取元素的events (事件列表)
然后遍历这个事件列表 判断type是不是事件列表的key 是的话就执行事件
在执行列表函数的时候会判断返回值
如果返回false 还可以组织事件冒泡 和 默认行为
win.gevent = { 
handle : function(event){ 
var all, handlers; 
//包装event 
event = arguments[0] = gevent.fix( event || window.event ); event.currentTarget = this; 
//这里的........ 
var namespaces = event.type.split("."); 
event.type = namespaces.shift(); 
all = !namespaces.length; 
var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)"); 
//取这个元素的该行为 的 事件列表 
handlers = (data(this, "events") || {} )[event.type]; 
//遍历这个事件列表 执行该执行的东西 
for ( var j in handlers ) { 
var handler = handlers[j]; 
if ( all || namespace.test(handler.type) ) { 
// Pass in a reference to the handler function itself 
// So that we can later remove it 
// jq上的注释是是这么写的 把event的handler 引用这个事件 方便之后移除 
// 但是在remove里面 并没有用到event的handler 不知道这里到底有什么用 且有多个事件的时候这个事件被取代 
event.handler = handler; 
//执行事件 并且是用元素调用的事件 可以吧事件里面的this执行元素 ret为函数的返回值 
var ret = handler.apply(this, arguments); 
//如果有返回值 且返回值是false 执行阻止事件冒泡 阻止执行事件默认行为 
if( ret !== undefined ){ 
event.result = ret; 
if ( ret === false ) { 
event.preventDefault(); 
event.stopPropagation(); 
} 
} 
} 
} 
}, 
props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), 
fix : function(event){ 
//new setEvent会给event给以个expando属性 如果有中个属性 说明已经生成了event了 不需要在次对event进行包装 
if ( event[expando] ) 
return event; 
//保留一个原始的event 
// new一个新的event 这个与原始的event是不同的 
var originalEvent = event; 
event = new setEvent( originalEvent ); 
//获得原始event的属性值 有哪些属性值 见 this.props 
for ( var i = this.props.length, prop; i; ){ 
prop = this.props[ --i ]; 
event[ prop ] = originalEvent[ prop ]; 
} 
//将目标元素同一成event.target 
if ( !event.target ) 
event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either 
//如果发现是文本节点 取他的父节点 
if ( event.target.nodeType == 3 ) 
event.target = event.target.parentNode; 
if ( !event.relatedTarget && event.fromElement ) 
event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement; 
return event; 
} 
} 
win.setEvent = function(src){ 
// Allow instantiation without the 'new' keyword 
// Event object 
if( src && src.type ){ 
this.originalEvent = src; 
this.type = src.type; 
// Event type 
}else 
this.type = src; 
// timeStamp is buggy for some events on Firefox(#3843) 
// So we won't rely on the native value 
this.timeStamp = now(); 
// Mark it as fixed 
this[expando] = true; 
} 
function returnFalse(){ 
return false; 
} 
function returnTrue(){ 
return true; 
} 
setEvent.prototype = { 
preventDefault: function() { 
var e = this.originalEvent; 
if( !e ) 
return; 
// if preventDefault exists run it on the original event 
if (e.preventDefault) 
e.preventDefault(); 
// otherwise set the returnValue property of the original event to false (IE) 
e.returnValue = false; 
}, 
stopPropagation: function() { 
var e = this.originalEvent; 
if( !e ) 
return; 
// if stopPropagation exists run it on the original event 
if (e.stopPropagation) 
e.stopPropagation(); 
// otherwise set the cancelBubble property of the original event to true (IE) 
e.cancelBubble = true; 
}, 
stopImmediatePropagation:function(){ 
this.isImmediatePropagationStopped = returnTrue; 
this.stopPropagation(); 
}, 
isImmediatePropagationStopped: returnFalse 
};

一个完整的例子
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> 
<title></title> 
</head> 
<body> 
<div id="vv" style="height:200px;width:200px; background-color:#f00; padding:20px;"> 
<div id="xx" style="height:200px;width:200px; background-color:#000000;"></div> 
</div> 
<script type="text/javascript"> 
(function(doc,undefined){ 
function now(){ 
return +new Date; 
}; 
var win = this, 
expando = "jQuery" + now(), 
uuid = 0, 
cache = {}; 
win.data = function(elem, name, data){ 
var id = elem[expando]; 
if(!id) 
id = elem[expando] = ++uuid; 
if(name&&!cache[id]) 
cache[id] = {}; 
if(data !== undefined) 
cache[id][name] = data; 
return name 
? cache[id][name] 
: id; 
} 
win.removeData = function(elem, name){ 
var id = elem[expando]; 
if (name){ 
if (cache[id]) { 
delete cache[id][name]; 
name = ""; 
for ( name in cache[ id ] ) 
break; 
if ( !name ) 
removeData(elem); 
} 
}else{ 
try { 
delete elem[expando]; 
} catch(e){ 
if ( elem.removeAttribute ) 
elem.removeAttribute( expando ); 
} 
delete cache[id]; 
} 
} 
win.each = function( object, callback, args ) { 
var name, i = 0, length = object.length; 
if ( args ) { 
if ( length === undefined ) { 
for ( name in object ) 
if ( callback.apply( object[ name ], args ) === false ) 
break; 
} else 
for ( ; i < length; ) 
if ( callback.apply( object[ i++ ], args ) === false ) 
break; 
} else { 
if ( length === undefined ) { 
for ( name in object ) 
if ( callback.call( object[ name ], name, object[ name ] ) === false ) 
break; 
} else 
for ( var value = object[0]; 
i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} 
} 
return object; 
} 
win.gevent = { 
guid : 1, 
add : function (elem, types, handler){ 
if ( elem.nodeType == 3 || elem.nodeType == 8 ) 
return; 
if ( elem.setInterval && elem != window ) 
elem = window; 
//给函数一个唯一标识的索引 方便后面删除该事件 
if ( !handler.guid ) 
handler.guid = this.guid++; 
//获得该元素的events handle 下的数据 
var events = data(elem, "events") || data(elem, "events", {}), 
handle =data(elem, "handle") || data(elem, "handle", function(){ 
//gevent.handle才是各种行为触发后会执行的函数 
gevent.handle.apply(arguments.callee.elem, arguments); 
}); 
handle.elem = elem; 
//遍历事件名 因为可以是 click mouseover 
each(types.split(/\s+/), function(index, type) { 
var namespaces = type.split("."); 
//获得事件名 
type = namespaces.shift(); 
//去掉点后面的东西 是个特殊的命名 在删除的时候可以指定删除他 如 click.d 
//用事件的type 记录住这个特殊的命名 
handler.type = namespaces.slice().sort().join("."); 
//获得该事件是否已经存在events 这个对象里面了 
var handlers = events[type]; 
//如果不存在该事件 给元素绑定该事件 
if (!handlers) { 
handlers = events[type] = {}; 
if (elem.addEventListener) 
elem.addEventListener(type, handle, false); 
else if (elem.attachEvent) 
elem.attachEvent("on" + type, handle); 
} 
//吧函数放到元素的该事件的列表里面 
handlers[handler.guid] = handler; 
}); 
elem = null; 
}, 
remove: function(elem, types, handler) { 
if ( elem.nodeType == 3 || elem.nodeType == 8 ) 
return; 
//获取这个元素的所有行为列表 如 {click:{},mouseocer:{}} 
var events = data(elem, "events"), ret, index; 
if(events){ 
//如果没出入行为类型 则删除这个元素的所有事件 
//如果传入的是.xx这种形式的 把所有行为的包含.xx命名的全部干掉 
if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") ){ 
for ( var type in events ) 
this.remove( elem, type + (types || "") ); 
}else{ 
//不知道干嘛的 
if ( types.type ) { 
handler = types.handler; 
types = types.type; 
} 
//因为删除事件可以一次支持删除多个 如click mouseover 所有要遍历删除 
each(types.split(/\s+/),function(index, type){ 
var namespaces = type.split("."); 
type = namespaces.shift(); 
var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)"); 
if ( events[type] ) { 
//如果传了第3个参数 函数 则删除这个事件 
if ( handler ) 
delete events[type][handler.guid]; 
else{ 
//遍历中个这个的所有行为 
for ( var handle in events[type] ){ 
// Handle the removal of namespaced events 
//删除有特殊命名的函数 
//如果没有特殊命名 正则 则是/^|..|$/ 可以匹配空 所以也能删除掉没有特殊命名的函数 
if ( namespace.test(events[type][handle].type) ) 
delete events[type][handle]; 
} 
} 
} 
for ( ret in events[type] ) break; 
//如果events[type]变成空的了 也就是{} 删除这个元素的的绑定事件 
if ( !ret ) { 
if (elem.removeEventListener) 
elem.removeEventListener(type, data(elem, "handle"), false); 
else if (elem.detachEvent) 
elem.detachEvent("on" + type, data(elem, "handle")); 
ret = null; 
delete events[type]; 
} 
}); 
} 
for ( ret in events ) break; 
//如果发现元素的整个events都是空的了 
//清空掉handle 并且清空掉他所有的引用 
if ( !ret ) { 
var handle = data( elem, "handle" ); 
if ( handle ) handle.elem = null; 
removeData( elem, "events" ); 
removeData( elem, "handle" ); 
} 
} 
}, 
handle : function(event){ 
var all, handlers; 
//包装event 
event = arguments[0] = gevent.fix( event || window.event ); 
event.currentTarget = this; 
//这里的........ 
var namespaces = event.type.split("."); 
event.type = namespaces.shift(); 
all = !namespaces.length; 
var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)"); 
//取这个元素的该行为 的 事件列表 
handlers = (data(this, "events") || {} )[event.type]; 
//遍历这个事件列表 执行该执行的东西 
for ( var j in handlers ) { 
var handler = handlers[j]; 
if ( all || namespace.test(handler.type) ) { 
// Pass in a reference to the handler function itself 
// So that we can later remove it 
// jq上的注释是是这么写的 把event的handler 引用这个事件 方便之后移除 
// 但是在remove里面 并没有用到event的handler 不知道这里到底有什么用 且有多个事件的时候这个事件被取代 
event.handler = handler; 
//执行事件 并且是用元素调用的事件 可以吧事件里面的this执行元素 ret为函数的返回值 
var ret = handler.apply(this, arguments); 
//如果有返回值 且返回值是false 执行阻止事件冒泡 阻止执行事件默认行为 
if( ret !== undefined ){ 
event.result = ret; 
if ( ret === false ) { 
event.preventDefault(); 
event.stopPropagation(); 
} 
} 
} 
} 
}, 
props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), 
fix : function(event){ 
//new setEvent会给event给以个expando属性 如果有中个属性 说明已经生成了event了 不需要在次对event进行包装 
if ( event[expando] ) 
return event; 
//保留一个原始的event 
// new一个新的event 这个与原始的event是不同的 
var originalEvent = event; 
event = new setEvent( originalEvent ); 
//获得原始event的属性值 有哪些属性值 见 this.props 
for ( var i = this.props.length, prop; i; ){ 
prop = this.props[ --i ]; 
event[ prop ] = originalEvent[ prop ]; 
} 
//将目标元素同一成event.target 
if ( !event.target ) 
event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either 
//如果发现是文本节点 取他的父节点 
if ( event.target.nodeType == 3 ) 
event.target = event.target.parentNode; 
if ( !event.relatedTarget && event.fromElement ) 
event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement; 
return event; 
} 
} 
win.setEvent = function(src){ 
// Allow instantiation without the 'new' keyword 
// Event object 
if( src && src.type ){ 
this.originalEvent = src; 
this.type = src.type; 
// Event type 
}else 
this.type = src; 
// timeStamp is buggy for some events on Firefox(#3843) 
// So we won't rely on the native value 
this.timeStamp = now(); 
// Mark it as fixed 
this[expando] = true; 
} 
function returnFalse(){ 
return false; 
} 
function returnTrue(){ 
return true; 
} 
setEvent.prototype = { 
preventDefault: function() { 
var e = this.originalEvent; 
if( !e ) 
return; 
// if preventDefault exists run it on the original event 
if (e.preventDefault) 
e.preventDefault(); 
// otherwise set the returnValue property of the original event to false (IE) 
e.returnValue = false; 
}, 
stopPropagation: function() { 
var e = this.originalEvent; 
if( !e ) 
return; 
// if stopPropagation exists run it on the original event 
if (e.stopPropagation) 
e.stopPropagation(); 
// otherwise set the cancelBubble property of the original event to true (IE) 
e.cancelBubble = true; 
}, 
stopImmediatePropagation:function(){ 
this.isImmediatePropagationStopped = returnTrue; 
this.stopPropagation(); 
}, 
isImmediatePropagationStopped: returnFalse 
}; 
})(document); 
var $ = function(id){return document.getElementById(id)} 
var a = function(){alert(1)} 
window.onload = function(){ 
gevent.add($('xx'),'click',a); 
gevent.add($('xx'),'click',function(){alert(1)}); 
gevent.add($('xx'),'click',function(){alert(2)}); 
gevent.add($('xx'),'click',function(){alert(3)}); 
gevent.add($('xx'),'click.xx',function(){alert(4)}); 
} 
</script> 
</body> 
</html>
Javascript 相关文章推荐
javascript实现的使用方向键控制光标在table单元格中切换
Nov 17 Javascript
JavaScript模拟可展开、拖动与关闭的聊天窗口实例
May 12 Javascript
jQuery设置Easyui校验规则(推荐)
Nov 21 Javascript
微信小程序教程系列之视图层的条件渲染(10)
Apr 19 Javascript
JavaScript+HTML5实现的日期比较功能示例
Jul 12 Javascript
bootstrap Table的一些小操作
Nov 01 Javascript
node vue项目开发之前后端分离实战记录
Dec 13 Javascript
配置node服务器并且链接微信公众号接口配置步骤详解
Jun 21 Javascript
基于Vue SEO的四种方案(小结)
Jul 01 Javascript
解决layui laydate 时间控件一闪而过的问题
Sep 28 Javascript
jQuery实现高度灵活的表单验证功能示例【无UI】
Apr 30 jQuery
详解Vue之计算属性
Jun 20 Javascript
JQuery扩展插件Validate—6 radio、checkbox、select的验证
Sep 05 #Javascript
JQuery扩展插件Validate—4设置错误提示的样式
Sep 05 #Javascript
JQuery扩展插件Validate 3通过参数设置错误信息
Sep 05 #Javascript
JQuery扩展插件Validate 2通过参数设置验证规则
Sep 05 #Javascript
JQuery扩展插件Validate 1 基本使用方法并打包下载
Sep 05 #Javascript
JQuery扩展插件Validate 5添加自定义验证方法
Sep 05 #Javascript
分享20多个很棒的jQuery 文件上传插件或教程
Sep 04 #Javascript
You might like
PHP 出现乱码和Sessions验证问题的解决方法!
2008/12/06 PHP
php中使用preg_match_all匹配文章中的图片
2013/02/06 PHP
ThinkPHP3.1的Widget新用法
2014/06/19 PHP
php生成图片缩略图的方法
2015/04/07 PHP
php正则匹配文章中的远程图片地址并下载图片至本地
2015/09/29 PHP
php获取网站根目录物理路径的几种方法(推荐)
2017/03/04 PHP
PHP实现根据数组的值进行分组的方法
2017/04/20 PHP
yii2项目实战之restful api授权验证详解
2017/05/20 PHP
关于PHP转换超过2038年日期出错的问题解决
2017/06/28 PHP
Google Map API更新实现用户自定义标注坐标
2009/07/29 Javascript
JavaScript数据类型之基本类型和引用类型的值
2015/04/01 Javascript
SpringMVC返回json数据的三种方式
2015/12/10 Javascript
Javascript获取数组中的最大值和最小值的方法汇总
2016/01/01 Javascript
浅谈javascript基础之客户端事件驱动
2016/06/10 Javascript
JQuery EasyUI学习教程之datagrid 添加、修改、删除操作
2016/07/09 Javascript
探讨AngularJs中ui.route的简单应用
2016/11/16 Javascript
完美解决node.js中使用https请求报CERT_UNTRUSTED的问题
2017/01/08 Javascript
AngularJS 将再发布一个重要版本 然后进入长期支持阶段
2018/01/31 Javascript
Vue.js自定义指令学习使用详解
2019/10/19 Javascript
如何用vue-cli3脚手架搭建一个基于ts的基础脚手架的方法
2019/12/12 Javascript
利用JS如何获取form表单数据
2019/12/19 Javascript
virtualenv 指定 python 解释器的版本方法
2018/10/25 Python
python opencv 实现对图像边缘扩充
2020/01/19 Python
Python Django form 组件动态从数据库取choices数据实例
2020/05/19 Python
HTML5 创建canvas元素示例代码
2014/06/04 HTML / CSS
使用HTML5和CSS3制作一个模态框的示例
2018/03/07 HTML / CSS
澳大利亚在线消费电子产品商店:TobyDeals
2020/01/05 全球购物
计算机毕业大学生推荐信
2013/12/01 职场文书
《掌声》教学反思
2014/02/23 职场文书
物流专业专科生职业生涯规划书
2014/09/14 职场文书
2014年宣传思想工作总结
2014/12/10 职场文书
工作保证书
2015/01/17 职场文书
社区六一儿童节活动总结
2015/02/11 职场文书
2015年出纳个人工作总结
2015/04/02 职场文书
党员读书活动心得体会
2016/01/14 职场文书
Python实战之OpenCV实现猫脸检测
2021/06/26 Python