jQuery 的事件机制由以下几部分组成:
1、利用 jQuery.Event(event | type[, props]) 构造函数创建可读写的 jQuery事件对象 $event, 该对象即可以是原生事件对象 event 的增强版,也可以是用户自定义事件,第二个参数 props 可以是一个对象, $event 会复制这个对象的属性来增强自己;
2、依赖 jQuery.event.fix(event),将原生的事件对象 event 修正为一个 $event 对象,并对该 $event 的属性以及方法统一接口。该方法在内部调用了 jQuery.Event(event) 构造函数。
3、由核心组件 jQuery.cache 实现注册事件处理程序的存储,实际上绑定在 DOM元素上的事件处理程序只有一个,即 jQuery.cache[elem[expando]].handle 中存储的函数,该函数在内部调用 jQuery.event.dispatch(event) 实现对该DOM元素特定事件的缓存的访问,并依次执行这些事件处理程序。
4、jQuery.event.dispatch(event) 方法会依次调用该DOM元素(this)上绑定的特定事件类型(event.type)的所有事件处理程序,并通过检测 handleObj.selector 与其后代元素(这里指从 event.target 到 this 路径上的所有DOM节点)的匹配情况,来处理基于选择器过滤的事件委托机制。jQuery.event.dispatch(event) 方法在进入逻辑时就调用 jQuery.event.fix(event) 方法对 event 对象进行修正。
5、jQuery.event.dispatch(event) 方法在处理事件委托机制时,依赖委托节点在DOM树的深度安排优先级,委托的DOM节点层次越深,其执行优先级越高。而其对于stopPropagation的处理有些特殊,在事件委托情况下并不一定会调用绑定在该DOM元素上的该类型的所有事件处理程序,而依赖于委托的事件处理程序的执行结果,如果低层委托的事件处理程序声明了停止冒泡,那么高层委托的事件以及自身绑定事件就不会被执行,这拓展了 DOM 委托机制的功能。
6、jQuery.event.trigger(event | type, data, elem, onlyHandlers) 方法提供开发人员以程序方式触发特定事件的接口,该方法的第一个参数可以是 $event/ event 对象 ,也可以是某个事件类型的字符串 type; 第二个参数 data 用于扩展该事件触发时事件处理程序的参数规模,用于传递一些必要的信息。 elem参数表示触发该事件的DOM元素;最后该方法在默认情况下,其事件会冒泡,并且在有默认动作的情况下执行默认行为,但是如果指定了 onlyHandlers 参数,该方法只会触发绑定在该DOM元素上的事件处理程序,而不会引发冒泡和默认动作,也不会触发特殊的 trigger 行为。
7、namespace 命名空间机制,namespace 机制可以对事件进行更为精细的控制,开发人员可以指定特定空间的事件,删除特定命名空间的事件,以及触发特定命名空间的事件。这使得对事件处理机制的功能更加健壮。
8、jQuery.event.add(elem, types, handler, data, selector) 方法用于给特定elem元素添加特定的事件 types([type.namespace, type.namespace, ...])的事件处理程序 handler, 通过第四个参数 data 增强执行当前 handler 事件处理程序时的 $event.data 属性,以提供更灵活的数据通讯,而第五个元素用于指定基于选择器的委托事件。
9、与之相反的,jQuery.event.remove(elem, types, handler, selector, mappedTypes) 方法用于删除该elem元素中满足特定选择器 selector 和特定事件类型 types 的事件处理程序 handler。 第五个参数 mappedTypes 只在该方法内部调用时使用,用于移除所有事件(不限事件类型,但可能有命名空间)时该参数才为 true,表示不需要检测类型是否匹配就可以删除事件处理程序(主要是用于处理 mouseenter 这类高阶事件,因为其存储在 mouseover 之中)。
10、高阶事件origType 与原生事件type: 我们将浏览器原生支持的事件称为原生事件,比如 mouseover; 而将基于原生事件进行增强的事件称为高阶事件 origType,比如 mouseenter,这里的 orig 字样表示这是用户输入的原始事件名。一般来说,高阶事件依赖于特定的原生事件才能实现,比如 mouseenter 来说,其 jQuery.event.specail.mouseenter.deletype = mouseover ,这说明 mouseenter 高阶事件依赖于 mouseover 原生事件来实现,相对来说, mouseenter 的事件处理程序需要某些执行条件。
11、模拟事件并立刻触发 jQuery.event.simulate(type, elem, event, bubble) 方法,可用于在DOM元素 elem 上模拟自定义事件类型 type,参数 bubble用于指定该事件是否可冒泡,event 参数表示 jQuery 事件对象 $event。 模拟事件通过事件对象的isSimulated属性为 true 表示这是模拟事件。该方法内部调用 trigger() 逻辑 或 dispatch() 逻辑立刻触发该模拟事件。该方法主要用于修正浏览器事件的兼容性问题,比如模拟出可冒泡的 focusin/ focusout 事件,修正IE中 change 事件的不可冒泡问题,修正IE中 submit事件不可冒泡问题。 (在jQuery 2.0 中只需要修正 focusin/ focusout 事件)
12、jQuert.event.special 对象用于某些事件类型的特殊行为和属性。比如 load 事件拥有特殊的 noBubble 属性,可以防止该事件的冒泡而引发一些错误。总的来说,有这样一些方法和属性:
setup() —— 若指定该方法,其在绑定事件处理程序(addEventListener)前执行,如果返回 false, 才绑定事件处理程序;
teardown() —— 若指定该方法,其在移除事件处理程序(removeEventListener)前执行,如果返回 false,才移除事件处理程序;
trigger() —— 若指定该方法,在执行 $.event.trigger 核心逻辑前调用该方法,如果返回 false,跳出 $.event.trigger 函数;
handle(event) —— 特殊的事件处理程序,若指定该方法,会替代其原来的事件处理程序 handleObj.handler 而被调用。
preDispatch(elem, event) —— 若指定该方法,在执行 $.event.dispatch 核心逻辑前调用该方法,如果返回 false,跳出 $.event.dispatch 方法。
postDispatch(elem, event) —— 若指定该方法,在执行 $.event.dispatch 核心逻辑后调用该函数,进行额外调度。
noBubble —— 指定该事件类型不能进行冒泡。
deleType, bindType —— 指定该事件类型的低阶事件类型,deleType 表示委托, bindType 表示非委托。
jQuery 1.9.1 版本实际上公开的API包括, on、 off、 trigger、triggerHandler 四个接口。
bind、unbind、delegate、undelegate、one 方法仅仅是 on/off API的简单外观模式,在编写jQuery代码时可以避免。
on/one 函数的核心逻辑依赖于 jQuery.event.add()
off 函数依赖于 jQuery.event.remove()
trigger/ triggerHandler 依赖于 jQuery.event.trigger(),主要用于通过程序触发事件,而不是依赖于用户交互,这些事件可以是自定义事件,也可以是DOM规范的事件, jQuery.event.trigger() 还可以进行冒泡和执行特定事件的默认行为。
jQuery.event.dispatch() 方法用于调度地执行特定的事件处理程序,除了DOM规范的事件之外,还包括模拟基础事件而产生的 focusin/ focusout(可冒泡),以及便利的 mouseenter/ mouseout(只会在当前元素触发该事件,不冒泡),以及自定义事件 。dispatch是事件系统实现的内部机制,并不对外暴露。
1、jQuery.Event 构造函数
jQuery.Event 是一个构造函数,其创建一个可读写的jQuery事件对象(我们约定,后面称之为 $event,将浏览器原生事件对象称为 event,以示区分)。由原生事件对象生成的 $event 对象保留了对这个原生事件对象 event 的引用($event.originalEvent)。我们绑定的事件处理程序所处理的事件对象都是 $event。
该方法也可以传递一个自定义事件的类型名,用于生成用户自定义事件对象。
jQuery.Event = function(src, props) { if (!(this instanceof jQuery.Event)) { return new jQuery.Event(src, props); } // src 是原生事件对象 event if (src && src.type) { this.originalEvent = src; // $event.originalEvent 保留对原生事件对象 event 的引用 this.type = src.type; // $event 的 isDefaultPrevented 属性应与 原生事件对象 event 保持一致 this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; // src 是 type 时 } else { this.type = src; } // 扩展 $event 对象 if (props) { jQuery.extend(this, props); } this.timeStamp = src && src.timeStamp || jQuery.now(); // 时间戳属性,这是 DOM3 Event 新增属性 this[jQuery.expando] = true; // 表示该事件对象是 $event, jQuery事件对象 }; jQuery.Event.prototype = { isDefaultPrevented: returnFalse, // 默认情况下,这三个属性都是 false isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse, preventDefault: function() { // 同时修改 $event 和 event var e = this.originalEvent; this.isDefaultPrevented = returnTrue; e && e.preventDefault(); }, stopPropagation: function() { var e = this.originalEvent; this.isPropagationStopped = returnTrue; e && e.stopPropagation(); }, stopImmediatePropagation: function() { this.isImmediatePropagationStopped = returnTrue; this.stopPropagation(); } };
2、$Event 对象的跨浏览器兼容性
jQuery 利用 jQuery.event.fix() 来解决跨浏览器的兼容性问题,统一接口。除该核心方法外,统一接口还依赖于 (jQuery.event) props、 fixHooks、keyHooks、mouseHooks 等数据模块。
props 存储了原生事件对象 event 的通用属性,keyHook.props 存储键盘事件的特有属性,而mouseHooks.props 存储鼠标事件的特有属性。
keyHooks.filter 和 mouseHooks.filter 两个方法分别用于修改键盘和鼠标事件的属性兼容性问题,用于统一接口。比如 event.which 通过 event.charCode 或 event.keyCode 或 event.button 来标准化。
最后 fixHooks 对象用于缓存不同事件所属的事件类别,比如
fixHooks['click'] === jQuery.event.mouseHooks;
fixHooks['keydown'] === jQuery.event.keyHooks;
fixHooks['focusin'] === {};
这个过程只在第一次处理时进行,再次处理时可以依赖缓存实现性能优化。
fix: function(event) { if (event[jQuery.expando]) { // 若该对象已是 $event ,不需要 fix 过程。 return event; } // 依赖原生 event 对象创建一个可读写的 $event 对象并且标准化该对象的属性 var i, prop, copy, type = event.type, originalEvent = event, fixHook = this.fixHooks[type]; // 该事件类型的 fixHook if (!fixHook) { // 初始化处理 /^(?:mouse|contextmenu)|click/ /^key/ fixHook = this.fixHooks[type] = rmouseEvent.test(type) ? this.mouseHooks : rkeyEvent.test(type) ? this.keyHooks : {}; } // 由原生event创建的$event对象,目前只有 type、 originalEvent、timestamp 等属性。 event = new jQuery.Event(originalEvent); // copy 存储了当前事件类型的所有属性列表,由通用属性 + 特有属性组合而成 copy = fixHook.props ? this.props.concat(fixHook.props) : this.props; // 将原生对象的对应属性复制到 $event 对象中 i = copy.length; while (i--) { prop = copy[i]; event[prop] = originalEvent[prop]; } // 处理通用属性的浏览器兼容性问题 // .... // 处理特定类型属性的浏览器兼容性问题 fixHook.filter && fixHook.filter(event, originalEvent) return event; //返回 $event },
3、缓存 与 handleObj
我们添加的事件处理函数并不直接绑定在DOM元素上,事实上,jQuery将我们定义的事件处理程序都存储在该DOM元素对应的缓存 jQuery._data(elem) 上的events 属性中,events 对象按照不同的事件类型存放不同的事件处理程序,比如下图中的 click是其中的一种事件类型。
这里的 click (events[type])是一个数组结构,数组的每个元素是一个 handleObj 对象,其存储了 jQuery事件机制实现所必要的属性,比如命名空间机制 namespace; 附加数据机制 data(附加到 $event.data 中);事件委托机制 selector、needsContext; handleObj 与 handler 关联机制 guid ; 高阶事件实现机制 origType、type 等。实际我们所定义的事件处理函数将会存储在 handleObj.handler 属性中,该函数通过全局唯一的guid属性实现与 handleObj 的通信。
下面观察 jQuery.event.add() 和 jQuery.event.remove() 方法的源码:
jQuery.event.add() 的核心逻辑:
1、 初始化 elem 元素的事件缓存 (events、handle、events[type]);
2、 给事件处理程序 handler 一个全局唯一标识 guid(如果已有,就不需要重新赋值),该标识也将赋值给对应的 handleObj,作为通信渠道;
3、 创建一个handleObj,该 handleObj 拥有 selector、guid、handler、data、needsContext、type、origType、namespace 属性;
4、 将该 handleObj 添加到 events[type] 队列中,委托事件在前,直接绑定事件在后,保持有序。
add: function(elem, types, handler, data, selector) { var events, len, handleObjIn, special, eventHandle, handleObj, handlers, namespaces, elemData = jQuery._data(elem); if (!elemData) { // 如果该 elem 不能拥有数据 或是 text/comment 节点 return; } // 调用该 add() 方法时可以传递一个定制对象来代替 handler 事件处理函数,我觉得用处不大 if (handler.handler) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // 确保该事件处理程序拥有一个唯一标识, 我们通过这个标识实现查询和移除remove操作,通过该属性也可以实现移除多个相同函数。 if (!handler.guid) { handler.guid = jQuery.guid++; } // 如果这是第一次给该元素添加事件,初始化处理 events 和 handle 属性 events = elemData.events; if (!events) { events = elemData.events = {}; } eventHandle = elemData.handle; if (!eventHandle) { eventHandle = elemData.handle = function(e) { // 定义真*事件处理程序, 如果在页面 unload 后触发该事件; 在执行默认行为时(trigger)时避免事件的二次冒泡执行 if (typeof jQuery !== 'undefined' && (!(e && jQuery.event.triggered === e.type))) { return jQuery.event.dispatch.apply(eventHandle.elem, arguments); // 通过dispatch进行调度分发 } else { return; } }; // Add elem as a property of the handle to prevent a memory leak with IE non-native events eventHandle.elem = elem; } // Handle multiple events separated by space jQuery(..).bind("mouseover mouseout", fn); types = (types || "").match(core_rnotwhite) || [""]; len = types.length; while (len--) { var tmp = rtypenamespace.exec(types[len]) || []; // 必定返回一个数组 var type = origType = tmp[1]; // 原生类型 和 高阶类型 var namespaces = (tmp[2] || "").split(".").sort(); // 命名空间 special = jQuery.event.special[type] || {}; type = (selector ? special.delegateType : special.bindType) || type; // 修正原生事件类型 special = jQuery.event.special[type] || {}; // 该原生事件类型的特殊对象,这里主要是 add 和 setup // 处理 handleObj 对象,添加其必要属性 handleObj = jQuery.extend({ type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, needsContext: selector && jQuery.expr.match.needsContext.test(selector), namespace: namespaces.join(".") //经排序的命名空间,已经修正 }, handleObjIn); // 如果这是第一次添加该类型事件, 初始化该类型事件的队列 handlers = events[type]; if (!handlers) { handlers = events[type] = []; handlers.delegateCount = 0; // 初始化委托事件的计数为 0 //满足以下条件: 没定义special.setup 或是 调用该函数的结果是 false, 使用 addEventListener if (special.setup === undefined || special.setup.call(elem, data, namespaces, eventHandle) === false) { elem.addEventListener(type, eventHandle, false); } } // 如果有自己的特殊方法 add,调用该方法。 if (special.add) { special.add.call(elem, handleObj); if (!handleObj.handler.guid) { handleObj.handler.guid = handler.guid; } } // 委托事件放在前面,插入handleObj对象到元素特定事件类型的列表中。这是核心逻辑,保持有序和优先级 if (selector) { handlers.splice(handlers.delegateCount, 0, handleObj); handlers.delegateCount++; } else { handlers.push(handleObj); } // 记录状态, 用于事件优化。 好像没有什么作用.... jQuery.event.global[type] = true; } // 去除对 elem 的引用, 防止IE中的内存溢出 elem = null; },
jQuery.event.remove() 的核心逻辑是:
1、删除特定的事件处理程序,支持命名空间匹配、选择器匹配、handler匹配、类型匹配;
2、删除事件处理程序后,维护 jQuery.cache 的数据结构,及时清空或移除数据,及时解除事件的绑定;
remove: function(elem, types, handler, selector, mappedTypes) { var j, handleObj, tmp, origCount, t, events, special, handlers, type, namespaces, origType, elemData = jQuery.hasData(elem) && jQuery._data(elem); //确保该DOM元素有数据的情况下才获取数据 if (!(elemData && (events = elemData.events))) { return; } // Once for each type.namespace in types; type 可省略,只有 .namespace 比如 types[i] 为'.hello' types = (types || "").match(core_rnotwhite) || [""]; t = types.length; while (t--) { tmp = rtypenamespace.exec(types[t]) || []; type = origType = tmp[1]; namespaces = (tmp[2] || "").split(".").sort(); // 移除所有类型的事件,可以带 namespaces,也可以不带。设定参数mappedTypes为true 这样才能删除高阶事件origType,比如 mouseenter if (!type) { for (type in events) { jQuery.event.remove(elem, type + types[t], handler, selector, true); } continue; } special = jQuery.event.special[type] || {}; type = (selector ? special.delegateType : special.bindType) || type; //获取原生事件,高阶事件存储在原生事件中 handlers = events[type] || []; tmp = tmp[2] && new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)"); // 移除匹配的事件,origCount 表示队列的原始长度,后面用于对比是否有真删除事件。 origCount = j = handlers.length; while (j--) { handleObj = handlers[j]; // 如果 mappedTypes 或 用户输入类型与该事件处理对象的高阶类型相同 // 并且 没有指定事件处理程序(即任意) 或 该事件处理程序 guid 与 该事件处理对象 guid 匹配 // 并且 没有指定命名空间(对命名空间无需求) 或 命名空间匹配 // 并且 没有指定选择器(非委托) 或 选择器匹配 或 (选择器为 ** ,而且该处理对象有选择器) // '**' ---> 表示匹配所有有选择器的事件处理函数,即任何委托的事件处理函数 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); } } } // 后期维护:移除该类型的事件处理函数,并清除其缓存,如果已经不需要。避免在移除特殊事件的处理器时发生潜在的无限递归 if (origCount > 0 && handlers.length === 0) { if (!special.teardown || special.teardown.call(elem, namespaces, elemData.handle) === false) { jQuery.removeEvent(elem, type, elemData.handle); } delete events[type]; } } // 后期维护,及时处理元素的缓存 if (jQuery.isEmptyObject(events)) { delete elemData.handle; jQuery._removeData(elem, "events"); } },
相关推荐
《jQuery Mobile Event 深入解析》 在Web开发领域,jQuery Mobile是一个强大的框架,它专为触摸设备设计,提供了一套完整的用户界面组件和事件处理机制。标题"jquery.mobile.event"表明我们将深入探讨jQuery Mobile...
jQuery提供了事件绑定和解绑的功能,例如`jQuery.event.add`用于添加事件监听器,而`jQuery(window).unload()`用于在IE浏览器中清除事件监听器,避免内存泄漏问题。 2. **Ajax请求**: - **基本功能**:jQuery...
3. **事件处理**:JQuery提供了一套简洁的API来绑定和解绑事件,如`$(selector).on('event', handler)`和`$(selector).off('event')`。此外,还有事件冒泡处理和事件委托的概念。 4. **链式调用**:JQuery对象的...
《深入解析jQuery.loadmore.js:实现上拉加载与下拉刷新功能》 在网页开发中,用户界面的交互性越来越受到重视,其中“上拉加载”(Load More)和“下拉刷新”(Pull to Refresh)是提升用户体验的重要手段。今天...
jQuery Accordion提供了一系列可配置的选项,例如`active`指定初始激活的面板,`collapsible`控制是否允许所有面板同时关闭,`event`设定触发切换的事件类型,以及`header`定义标题的选择器等。开发者可以根据需求...
总结,jQuery.sortable插件通过简单的API和高效的事件处理机制,实现了div元素的拖动排序功能,为网页开发带来了极大的便利。通过对源码的深入理解,开发者可以更好地定制和优化排序行为,以满足不同项目的需求。
jQuery 1.7.2的主要改进之一是引入了全新的事件处理机制——事件代理(Event Delegation)。这一机制使得开发者可以为动态添加的元素绑定事件,而无需在每次元素创建时都重新绑定,大大提高了代码的效率和可维护性。...
EasyUI 使用 jQuery 的事件模型,通过`$(selector).bind('event', handler)`来监听事件。源码中,各种组件的交互事件如点击、改变等都有对应的事件处理器。同时,EasyUI 支持数据绑定,使动态更新视图变得简单。 5...
3. 阻止默认行为与事件冒泡:学习`event.preventDefault()`和`event.stopPropagation()`的应用场景。 第三天:jQuery动画 1. 动画效果:掌握`.animate()`方法,创建平滑的CSS属性变化动画。 2. 显示与隐藏:理解`....
在源码中,`jQuery.event.add`负责事件绑定,`jQuery.event.trigger`负责事件触发。这些代码揭示了事件委托、事件冒泡等概念,以及jQuery如何处理原生事件和自定义事件的。 Ajax功能是jQuery的重要组成部分,`$....
《jQuery redirect.js:实现表单重定向的JavaScript插件解析》 在Web开发中,jQuery是一个广泛使用的JavaScript库,它简化了DOM操作、事件处理、动画制作等任务。"jquery.redirect.js"是jQuery的一个插件,专为实现...
前端接收到JSON数据后,可以使用`$.ajax`或`$.getJSON`方法解析数据,并用这些数据动态创建新的树节点: ```javascript $("#treeview").on("expand", function (event, data) { $.getJSON("/loadNodes", { id: ...
在JavaScript和jQuery的世界里,无刷新上传是一种提升用户体验的重要...它们提供了丰富的定制选项和事件处理机制,帮助开发者构建高性能、用户体验良好的上传功能。在实际项目中,应根据具体需求选择合适的上传方案。
}事件解绑从jQuery.event.remove开始,这个方法会遍历元素的事件缓存,找到匹配的事件处理函数并执行解绑操作。在解绑过程中,如果未指定具体的事件处理函数,那么所有与事件类型相关的处理函数都会被解绑。总结...
《jQuery拖拽插件drag.js深度解析》 在网页开发中,实现元素的拖放功能是一种常见的交互设计,它能够提升用户体验,使用户能够通过直观的拖拽操作与页面进行互动。jQuery作为一款广泛使用的JavaScript库,提供了...
此外,jQuery还支持事件委托,如`$('parent').on('event', 'child', function() {...})`,使得动态添加的子元素也能响应事件。 动画效果是jQuery的一大亮点。`.fadeIn()`, `.slideUp()`, `.animate()`等方法使得...
- 分析 jQuery 1.4.2 的源码可以帮助我们理解其内部机制,如事件绑定的实现、选择器的解析过程等。这对于提升 JavaScript 技能和扩展自定义功能非常有帮助。 5. **实际应用示例** - 使用 jQuery 实现页面元素的...
jQuery的事件处理机制使得绑定和解绑事件变得简单。例如,`$(selector).click(fn)`用于为元素绑定点击事件,`$(selector).on('event', fn)`则可以绑定多种事件,而`$(selector).off('event')`用于解除事件绑定。 五...
2. **事件处理**:jQuery的事件处理机制使得绑定和触发事件变得简单。在商品结算页面中,当用户增加或减少商品数量,或者更改收货地址时,可以使用`.on('event', function() {...})`来监听这些事件,并执行相应的...
在Web开发中,jQuery、Ajax和JSON是三个关键的技术组件,它们共同构成了高效、异步的数据交互机制。这里我们将深入探讨如何使用jQuery结合Ajax技术来实现文件上传,并通过JSON进行数据解析。 首先,jQuery是一个...