浅谈jQuery事件绑定原理


Posted in Javascript onJanuary 02, 2015

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 相关文章推荐
json对象转字符串如何实现
Dec 02 Javascript
Extjs的FileUploadField文件上传出现了两个上传按钮
Apr 29 Javascript
jQuery中:button选择器用法实例
Jan 04 Javascript
JavaScript将字符串转换成字符编码列表的方法
Mar 19 Javascript
创建简单的node服务器实例(分享)
Jun 23 Javascript
vue插件vue-resource的使用笔记(小结)
Aug 04 Javascript
微信小程序 按钮滑动的实现方法
Sep 27 Javascript
webpack打包node.js后端项目的方法
Mar 10 Javascript
详解vuex 渐进式教程实例代码
Nov 27 Javascript
Vue 列表上下过渡效果的实例代码
Jun 25 Javascript
vue-router二级导航切换路由及高亮显示的实现方法
Jul 10 Javascript
JS常用正则表达式超全集(密码强度校验,金额校验,IE版本,IPv4,IPv6校验)
Feb 03 Javascript
js+jquery实现图片裁剪功能
Jan 02 #Javascript
javascript 构造函数方式定义对象
Jan 02 #Javascript
深入探寻javascript定时器
Jan 02 #Javascript
JavaScript中的Truthy和Falsy介绍
Jan 01 #Javascript
JavaScript中的null和undefined区别介绍
Jan 01 #Javascript
JavaScript中的全局对象介绍
Jan 01 #Javascript
原生javascript获取元素样式
Dec 31 #Javascript
You might like
Terran兵种介绍
2020/03/14 星际争霸
提升PHP执行速度全攻略(上)
2006/10/09 PHP
php 数组动态添加实现代码(最土团购系统的价格排序)
2011/12/30 PHP
用PHP即时捕捉PHP中的错误并发送email通知的实现代码
2013/01/19 PHP
PHP大小写问题:函数名和类名不区分,变量名区分
2013/06/17 PHP
php实现等比例压缩图片
2018/07/26 PHP
js 如何实现对数据库的增删改查
2012/11/23 Javascript
IE浏览器IFrame对象内存不释放问题解决方法
2014/08/22 Javascript
深入理解JS DOM事件机制
2016/08/06 Javascript
jstl中判断list中是否包含某个值的简单方法
2016/10/14 Javascript
用vue构建多页面应用的示例代码
2017/09/20 Javascript
vue-router路由懒加载和权限控制详解
2017/12/13 Javascript
使用Vue自定义指令实现Select组件
2018/05/24 Javascript
浅析Angular 实现一个repeat指令的方法
2019/07/21 Javascript
在vue项目中使用codemirror插件实现代码编辑器功能
2019/08/27 Javascript
解决elementUI 切换tab后 el_table 固定列下方多了一条线问题
2020/07/19 Javascript
详解PyTorch批训练及优化器比较
2018/04/28 Python
对python3 Serial 串口助手的接收读取数据方法详解
2019/06/12 Python
python 生成器和迭代器的原理解析
2019/10/12 Python
python输出pdf文档的实例
2020/02/13 Python
python实现堆排序的实例讲解
2020/02/21 Python
浅谈keras通过model.fit_generator训练模型(节省内存)
2020/06/17 Python
python中的插入排序的简单用法
2021/01/19 Python
Bjorn Borg官方网上商店:国际运动时尚品牌
2016/08/27 全球购物
改变生活的男士内衣:SAXX Underwear
2019/08/28 全球购物
公务员职业生涯规划书范文  
2014/01/19 职场文书
主题党日活动总结
2014/07/08 职场文书
2014年十一国庆节活动方案
2014/09/16 职场文书
酒店管理专业毕业生自我鉴定
2014/09/29 职场文书
单位作风建设剖析材料
2014/10/11 职场文书
民政局办理协议离婚(范本)
2014/10/25 职场文书
测量员岗位职责
2015/02/14 职场文书
硕士学位申请报告
2015/05/15 职场文书
JavaScript利用html5新方法操作元素类名详解
2021/11/27 Javascript
MySQL高级进阶sql语句总结大全
2022/03/16 MySQL
HTML 里 img 元素的 src 和 srcset 属性的区别详解
2023/05/21 HTML / CSS