mass Framework event模块 v9
本次升级借鉴了jQuery事件模块的许多代码,可谓是jQuery事件模块的改良版。
与原先一样,拆分为两块,event模块是支持新一代的浏览器的,如IE9,chrome5+, opera10+,safari5+;event_fix是对付IE678。
拆分后的好处,在标准浏览器中,我们就不要加载这么多代码,跑这么多注定要跳过的分支,有效地提升性能。
拆分后就有利于我对标准浏览器有一个新的了解,发现firefox成为最拖后腿的一位。它在滚轮事件,focusin, focusout的迟迟不合作,让我们不得不奠出eventSupport等利器。webkit系还需要模拟mouseenter, mouseleave事件。由于标准浏览器的原生属性不能被覆盖,比如我们用mouseover来冒充mouseenter,那么我们还得将它外包一层,这工作为$.Event来做。它本来就是用于摒蔽事件对象在各浏览器的差异性的,让IE也拥有W3C的调用接口。因此$.Event还是不能移到event_fix模块。
在jQuery中还有一个simulate方法,用于让事件对象伪装成另一类事件,根据最后一个参数在本层或整个DOM树中传播。但通过研读源码发现,它的最后参数总是true,并且它也多用于修复IE的事件派发,只有一处是用于FF。因此我把它放到event_fix中去了。
在$.event.fix方法中,我发现jQuery对原事件对象的属性复制是限死的,规定好了某些属性要被复制,因此伪事件对象在一些场合还是要访问originalEvent来干活。这里我做了一些改良。并且针对鼠标事件与键盘事件这两类事件的大补丁,我也分配好它们的归属。一个目标,减少无效的分支判定。jQuery在dispatch这个方法中实在做了许多判定了,因此跑得很慢,这会在一些持续触发的事件,如scroll, resize, mousemove等非常吃力。
即使在event模块中,事件系统还是要对一些事件进行特殊处理,分别是load, focus, blur, click, beforeunload。
再来看event_fix,只要是处理IE的事件代理,change与submit,还有就是事件对象对标准的跟随。这补丁模块总归要入土的,但现在它对大陆人来说还是必不可少。
事件模块是使用wrap方式进行,完全伪装原短对象在DOM的触发行为。许多神一样的代码是jQuery团队写的,偶只是照搬。另一些奇技淫巧虽然是我自创的,但给出足够的链接希望你们能看得懂。这一个框架最重要也是最复杂的一部分,许多框架的事件系统能搞成这样具够扩展性也极让人困惑。
event.js
//========================================= // 事件系统 v9 //========================================== define( "event" , top.dispatchEvent ? [ "$node" ] : [ "$event_fix" ], function ($) { var facade = $.event || ($.event = { //对某种事件类型进行特殊处理 special: {}, //对Mouse事件这一大类事件类型的事件对象进行特殊处理 fixMouse: function (event, real) { if (event.type === "mousewheel" ) { //处理滚轮事件 if ( "wheelDelta" in real) { //统一为±120,其中正数表示为向上滚动,负数表示向下滚动 // http://www.w3help.org/zh-cn/causes/SD9015 var delta = real.wheelDelta //opera 9x系列的滚动方向与IE保持一致,10后修正 if (window.opera && opera.version() < 10) delta = -delta; event.wheelDelta = Math.round(delta); //修正safari的浮点 bug } else if ( "detail" in real) { event.wheelDelta = -real.detail * 40; //修正FF的detail 为更大众化的wheelDelta } } } }), eventHooks = facade.special, rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, rtypenamespace = /^([^.]*)(?:\.(.+)|)$/, mouseEvents = "contextmenu,click,dblclick,mouseout,mouseover,mouseenter,mouseleave,mousemove,mousedown,mouseup,mousewheel," , eventMap = $.oneObject(mouseEvents, "Mouse" ), types = mouseEvents + ",keypress,keydown,keyup," + "blur,focus,focusin,focusout," + "abort,error,load,unload,resize,scroll,change,input,select,reset,submit" //input $.eventSupport = function (eventName, el) { el = el || document.createElement( "div" ); eventName = "on" + eventName; var ret = eventName in el; if (el.setAttribute && !ret) { el.setAttribute(eventName, "" ); ret = typeof el[eventName] === "function" ; el.removeAttribute(eventName); } el = null ; return ret; }; function Event(src, props) { if (!( this instanceof $.Event)) { return new Event(src, props); } this .originalEvent = {}; //保存原生事件对象 if (src && src.type) { this .originalEvent = src; //重写 this .type = src.type; } else { this .type = src; } this .defaultPrevented = false ; if (props) { $.mix( this , props); } this .timeStamp = new Date - 0; }; Event.prototype = { toString: function () { return "[object Event]" }, preventDefault: function () { //阻止默认行为 this .defaultPrevented = true ; var e = this .originalEvent if (e && e.preventDefault) { e.preventDefault(); } e.returnValue = false ; return this ; }, stopPropagation: function () { //阻止事件在DOM树中的传播 var e = this .originalEvent if (e && e.stopPropagation) { e.stopPropagation(); } //propagationStopped的命名出自 http://opera.im/kb/userjs/ e.cancelBubble = this .propagationStopped = true ; return this ; }, stopImmediatePropagation: function () { //阻止事件在一个元素的同种事件的回调中传播 this .isImmediatePropagationStopped = true ; this .stopPropagation(); return this ; } } $.Event = Event; $.mix(eventHooks, { load: { //此事件不能冒泡 noBubble: true }, click: { //处理checkbox中的点击事件 trigger: function () { if ( this .nodeName == "INPUT" && this .type === "checkbox" && this .click) { this .click(); return false ; } } }, focus: { //IE9-在不能聚焦到隐藏元素上,强制触发此事件会抛错 trigger: function () { if ( this !== document.activeElement && this .focus) { try { this .focus(); return false ; } catch (e) {} } }, delegateType: "focusin" }, blur: { trigger: function () { //blur事件的派发使用原生方法实现 if ( this === document.activeElement && this .blur) { this .blur(); return false ; } }, delegateType: "focusout" }, beforeunload: { postDispatch: function (event) { if (event.result !== void 0) { event.originalEvent.returnValue = event.result; } } } }); $.mix(facade, { //addEventListner API的支持情况:chrome 1+ FF1.6+ IE9+ opera 7+ safari 1+; //http://functionsource.com/post/addeventlistener-all-the-way-back-to-ie-6 add: function (elem, hash) { var elemData = $._data(elem), //取得对应的缓存体 types = hash.type, //原有的事件类型,可能是复数个 selector = hash.selector, //是否使用事件代理 handler = hash.handler; //回调函数 if (elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler) { return ; } hash.uniqueNumber = $.getUid(handler); //确保hash.uuid与fn.uuid一致 var events = elemData.events || (elemData.events = []), eventHandle = elemData.handle; if (!eventHandle) { elemData.handle = eventHandle = function (e) { return typeof $ !== "undefined" && (!e || facade.triggered !== e.type) ? facade.dispatch.apply(eventHandle.elem, arguments) : void 0; }; eventHandle.elem = elem; //由于IE的attachEvent回调中的this不指向绑定元素,需要强制缓存它 } types.replace($.rword, function (t) { var tns = rtypenamespace.exec(t) || [], type = tns[1]; var namespaces = (tns[2] || "" ).split( "." ).sort(); // 看需不需要特殊处理 var hook = eventHooks[type] || {}; // 事件代理与事件绑定可以使用不同的冒充事件 type = (selector ? hook.delegateType : hook.bindType) || type; hook = eventHooks[type] || {}; var handleObj = $.mix({}, hash, { type: type, origType: tns[1], namespace: namespaces.join( "." ) }); var handlers = events[type]; //初始化事件列队 if (!handlers) { handlers = events[type] = []; handlers.delegateCount = 0; if (!hook.setup || hook.setup.call(elem, namespaces, eventHandle) === false ) { if ($[ "@bind" ] in elem) { $.bind(elem, type, eventHandle) } } } if (hook.add) { hook.add.call(elem, handleObj); } //先处理用事件代理的回调,再处理用普通方式绑定的回调 if (selector) { handlers.splice(handlers.delegateCount++, 0, handleObj); } else { handlers.push(handleObj); } //用于优化fire方法 facade.global[type] = true ; }) //防止IE内在泄漏 elem = null ; }, //用于优化事件派发 global: {}, //移除目标元素绑定的回调 remove: function (elem, hash) { var elemData = $._data(elem), events, origType if (!(events = elemData.events)) return ; var types = hash.type || "" , selector = hash.selector, handler = hash.handler; types.replace($.rword, function (t) { var tns = rtypenamespace.exec(t) || [], type = origType = tns[1], namespaces = tns[2]; //只传入命名空间,不传入事件类型,则尝试遍历所有事件类型 if (!type) { for (type in events) { facade.unbind(elem, $.mix({}, hash, { type: type + t })); } return } var hook = eventHooks[type] || {}; type = (selector ? hook.delegateType : hook.bindType) || type; var handlers = events[type] || []; var origCount = handlers.length; namespaces = namespaces ? new RegExp( "(^|\\.)" + namespaces.split( "." ).sort().join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : null ; for ( var j = 0, handleObj; j < handlers.length; j++) { handleObj = handlers[j]; //如果事件类型相同,回调相同,命名空间相同,选择器相同则移除此handleObj if ((origType === handleObj.origType) && (!handler || handler.uniqueNumber === handleObj.uniqueNumber) && (!namespaces || namespaces.test(handleObj.namespace)) && (!selector || selector === handleObj.selector || selector === "**" && handleObj.selector)) { handlers.splice(j--, 1); if (handleObj.selector) { handlers.delegateCount--; } if (hook.remove) { hook.remove.call(elem, handleObj); } } } if (handlers.length === 0 && origCount !== handlers.length) { if (!hook.teardown || hook.teardown.call(elem, namespaces, elemData.handle) === false ) { if ($[ "@bind" ] in elem) { $.unbind(elem, type, elemData.handle) } } delete events[type]; } }) if ($.isEmptyObject(events)) { delete elemData.handle; $._removeData(elem, "events" ); //这里会尝试移除缓存体 } }, //通过传入事件类型或事件对象,触发事件回调,在整个DOM树中执行 trigger: function (event) { var elem = this ; //跳过文本节点与注释节点,主要是照顾旧式IE if (elem && (elem.nodeType === 3 || elem.nodeType === 8)) { return ; } var i, cur, old, ontype, handle, eventPath, bubbleType, type = event.type || event, namespaces = event.namespace ? event.namespace.split( "." ) : []; // focus/blur morphs to focusin/out; ensure we're not firing them right now if (rfocusMorph.test(type + facade.triggered)) { return ; } if (type.indexOf( "." ) >= 0) { //分解出命名空间 namespaces = type.split( "." ); type = namespaces.shift(); namespaces.sort(); } //如果从来没有绑定过此种事件,也不用继续执行了 if (!elem && !facade.global[type]) { return ; } // Caller can pass in an Event, Object, or just an event type string event = typeof event === "object" ? // 如果是$.Event实例 event.originalEvent ? event : // Object literal new $.Event(type, event) : // Just the event type (string) new $.Event(type); event.type = type; event.isTrigger = true ; event.namespace = namespaces.join( "." ); event.namespace_re = event.namespace ? new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : null ; ontype = type.indexOf( ":" ) < 0 ? "on" + type : "" ; //清除result,方便重用 event.result = void 0; if (!event.target) { event.target = elem; } //取得额外的参数 var data = $.slice(arguments); data[0] = event; //判定是否需要用到事件冒充 var hook = eventHooks[type] || {}; if (hook.trigger && hook.trigger.apply(elem, data) === false ) { return ; } //铺设往上冒泡的路径,每小段都包括处理对象与事件类型 eventPath = [ [elem, hook.bindType || type] ]; if (!hook.noBubble && !$.type(elem, "Window" )) { bubbleType = hook.delegateType || type; cur = rfocusMorph.test(bubbleType + type) ? elem : elem.parentNode; for (old = elem; cur; cur = cur.parentNode) { eventPath.push([cur, bubbleType]); old = cur; } //一直冒泡到window if (old === (elem.ownerDocument || document)) { eventPath.push([old.defaultView || old.parentWindow || window, bubbleType]); } } //沿着之前铺好的路触发事件 for (i = 0; i < eventPath.length && !event.propagationStopped; i++) { cur = eventPath[i][0]; event.type = eventPath[i][1]; handle = ($._data(cur, "events" ) || {})[event.type] && $._data(cur, "handle" ); if (handle) { handle.apply(cur, data); } //处理直接写在标签中的内联事件或DOM0事件 handle = ontype && cur[ontype]; if (handle && handle.apply && handle.apply(cur, data) === false ) { event.preventDefault(); } } event.type = type; //如果没有阻止默认行为 if (!event.defaultPrevented) { if ((!hook._default || hook._default.apply(elem.ownerDocument, data) === false ) && !(type === "click" && elem.nodeName == "A" )) { if (ontype && $.isFunction(elem[type]) && elem.nodeType) { old = elem[ontype]; if (old) { elem[ontype] = null ; } //防止二次trigger,elem.click会再次触发addEventListener中绑定的事件 facade.triggered = type; try { //IE6-8在触发隐藏元素的focus/blur事件时会抛出异常 elem[type](); } catch (e) {} delete facade.triggered; if (old) { elem[ontype] = old; } } } } return event.result; }, //执行用户回调,只在当前元素中执行 dispatch: function (e) { //如果不存在事件回调就没有必要继续进行下去 var eventType = e.type, handlers = (($._data( this , "events" ) || {})[eventType] || []) if (!handlers.length) { return ; } //摒蔽事件对象在各浏览器下的差异性 var event = $.event.fix(e), delegateCount = handlers.delegateCount, args = $.slice(arguments), hook = eventHooks[eventType] || {}, handlerQueue = [], ret, selMatch, matched, matches, handleObj, sel //重置第一个参数 args[0] = event; event.delegateTarget = this ; // 经典的AOP模式 if (hook.preDispatch && hook.preDispatch.call( this , event) === false ) { return ; } //收集阶段 //如果使用了事件代理,则先执行事件代理的回调, FF的右键会触发点击事件,与标签不符 if (delegateCount && !(event.button && eventType === "click" )) { for ( var cur = event.target; cur != this ; cur = cur.parentNode || this ) { //disabled元素不能触发点击事件 if (cur.disabled !== true || eventType !== "click" ) { selMatch = {}; matches = []; for ( var i = 0; i < delegateCount; i++) { handleObj = handlers[i]; sel = handleObj.selector; //判定目标元素(this)的孩子(cur)是否匹配(sel) if (selMatch[sel] === void 0) { selMatch[sel] = $(sel, this ).index(cur) >= 0 } if (selMatch[sel]) { matches.push(handleObj); } } if (matches.length) { handlerQueue.push({ elem: cur, matches: matches }); } } } } // 这是事件绑定的回调 if (handlers.length > delegateCount) { handlerQueue.push({ elem: this , matches: handlers.slice(delegateCount) }); } // 如果没有阻止事件传播,则执行它们 for (i = 0; i < handlerQueue.length && !event.propagationStopped; i++) { matched = handlerQueue[i]; event.currentTarget = matched.elem; for ( var j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped; j++) { handleObj = matched.matches[j]; //namespace,.namespace_re属性只出现在trigger方法中 if (!event.namespace || event.namespace_re && event.namespace_re.test(handleObj.namespace)) { event.data = handleObj.data; event.handleObj = handleObj; ret = ((eventHooks[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args); handleObj.times--; if (handleObj.times === 0) { //如果有次数限制并到用光所有次数,则移除它 facade.unbind(matched.elem, handleObj) } if (ret !== void 0) { event.result = ret; if (ret === false ) { event.preventDefault(); event.stopPropagation(); } } } } } if (hook.postDispatch) { hook.postDispatch.call( this , event); } return event.result; }, //修正事件对象,摒蔽差异性 fix: function (event) { if (!event.originalEvent) { var real = event; event = $.Event(real); //复制真实事件对象的成员 for ( var p in real) { if (!(p in event)) { event[p] = real[p] } } //如果不存在target属性,为它添加一个 if (!event.target) { event.target = event.srcElement || document; } //safari的事件源对象可能为文本节点,应代入其父节点 if (event.target.nodeType === 3) { event.target = event.target.parentNode; } event.metaKey = !! event.ctrlKey; // 处理IE678的组合键 var callback = facade[ "fix" + eventMap[event.type]] if ( typeof callback == "function" ) { callback(event, real) } } return event; } }); facade.bind = facade.add; facade.unbind = facade.remove; //以下是用户使用的API $.implement({ hover: function (fnIn, fnOut) { return this .mouseenter(fnIn).mouseleave(fnOut || fnIn); }, delegate: function (selector, types, fn, times) { return this .on(types, selector, fn, times); }, live: function (types, fn, times) { $.log( "$.fn.live() is deprecated" ) $( this .ownerDocument).on(types, this .selector, fn, times); return this ; }, one: function (types, fn) { return this .on(types, fn, 1); }, undelegate: function (selector, types, fn) { /*顺序不能乱*/ return arguments.length == 1 ? this .off(selector, "**" ) : this .off(types, fn, selector); }, die: function (types, fn) { $.log( "$.fn.die() is deprecated" ) $( this .ownerDocument).off(types, fn, this .selector || "**" , fn); return this ; }, fire: function () { var args = arguments; return this .each( function () { facade.trigger.apply( this , args); }); } }); //这个迭代器产生四个重要的事件绑定API on off bind unbind var rtypes = /^[a-z0-9_\-\.\s\,]+$/i "on_bind,off_unbind" .replace($.rmapper, function (_, method, mapper) { $.fn[method] = function (types, selector, fn) { if ( typeof types === "object" ) { for ( var type in types) { $.fn[method]( this , type, selector, types[type], fn); } return this ; } var hash = {}; for ( var i = 0; i < arguments.length; i++) { var el = arguments[i]; if ( typeof el == "number" ) { hash.times = el; } else if ( typeof el == "function" ) { hash.handler = el } if ( typeof el === "string" ) { if (hash.type != null ) { hash.selector = el.trim(); } else { hash.type = el.trim(); //只能为字母数字-_.空格 if (!rtypes.test(hash.type)) { throw new Error( "事件类型格式不正确" ) } } } } if (!hash.type) { throw new Error( "必须指明事件类型" ) } if (method === "on" && !hash.handler) { throw new Error( "必须指明事件回调" ) } hash.times = hash.times > 0 ? hash.times : Infinity; return this .each( function () { facade[mapper]( this , hash); }); } $.fn[mapper] = function () { // $.fn.bind $.fn.unbind return $.fn[method].apply( this , arguments); } }); types.replace($.rword, function (type) { //这里产生以事件名命名的快捷方法 eventMap[type] = eventMap[type] || (/key/.test(type) ? "Keyboard" : "HTML" ) $.fn[type] = function (callback) { return callback ? this .bind(type, callback) : this .fire(type); } }); /* mouseenter/mouseleave/focusin/focusout已为标准事件,经测试IE5+,opera11,FF10+都支持它们 详见http://www.filehippo.com/pl/download_opera/changelog/9476/ */ if (!+ "\v1" || !$.eventSupport( "mouseenter" )) { //IE6789不能实现捕获与safari chrome不支持 "mouseenter_mouseover,mouseleave_mouseout" .replace($.rmapper, function (_, type, fix) { eventHooks[type] = { delegateType: fix, bindType: fix, handle: function (event) { var ret, target = this , related = event.relatedTarget, handleObj = event.handleObj; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window if (!related || (related !== target && !$.contains(target, related))) { event.type = handleObj.origType; ret = handleObj.handler.apply( this , arguments); event.type = fix; } return ret; } } }); } //现在只有firefox不支持focusin,focusout事件,并且它也不支持DOMFocusIn,DOMFocusOut,不能像DOMMouseScroll那样简单冒充,Firefox 17+ if (!$.support.focusin) { "focusin_focus,focusout_blur" .replace($.rmapper, function (_, orig, fix) { var attaches = 0, handler = function (event) { event = facade.fix(event); $.mix(event, { type: orig, isSimulated: true }); facade.trigger.call(event.target, event); }; eventHooks[orig] = { setup: function () { if (attaches++ === 0) { document.addEventListener(fix, handler, true ); } }, teardown: function () { if (--attaches === 0) { document.removeEventListener(fix, handler, true ); } } }; }); } try { //FF需要用DOMMouseScroll事件模拟mousewheel事件 document.createEvent( "MouseScrollEvents" ); eventHooks.mousewheel = { bindType: "DOMMouseScroll" , delegateType: "DOMMouseScroll" } if ($.eventSupport( "mousewheel" )) { delete eventHooks.mousewheel; } } catch (e) {}; return $; }) |
event_fix.js
define( "event_fix" , !! document.dispatchEvent, [ "$node" ], function ($) { //模拟IE678的reset,submit,change的事件代理 var rformElems = /^(?:input|select|textarea)$/i var facade = $.event = { special: {}, fixMouse: function (event) { // 处理鼠标事件 http://www.w3help.org/zh-cn/causes/BX9008 var doc = event.target.ownerDocument || document; //safari与chrome下,滚动条,视窗相关的东西是放在body上 var box = document.compatMode == "BackCompat" ? doc.body : doc.documentElement event.pageX = event.clientX + (box.scrollLeft >> 0) - (box.clientLeft >> 0); event.pageY = event.clientY + (box.scrollTop >> 0) - (box.clientTop >> 0); //如果不存在relatedTarget属性,为它添加一个 if (!event.relatedTarget && event.fromElement) { //mouseover mouseout event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; } //标准浏览判定按下鼠标哪个键,左1中2右3 var button = event.button //IE event.button的意义 0:没有键被按下 1:按下左键 2:按下右键 3:左键与右键同时被按下 4:按下中键 5:左键与中键同时被按下 6:中键与右键同时被按下 7:三个键同时被按下 event.which = [0, 1, 3, 0, 2, 0, 0, 0][button]; //0现在代表没有意义 }, fixKeyboard: function (event) { event.which = event.charCode != null ? event.charCode : event.keyCode; } }; var special = facade.special function simulate(type, elem, event) { event = new $.Event(event); $.mix({ type: type, isSimulated: true }); $.event.trigger.call(elem, event); if (event.defaultPrevented) { event.preventDefault(); } } special.change = { setup: function () { if (rformElems.test( this .nodeName)) { // IE doesn't fire change on a check/radio until blur; trigger it on click // after a propertychange. Eat the blur-change in special.change.handle. // This still fires onchange a second time for check/radio after blur. if ( this .type === "checkbox" || this .type === "radio" ) { $( this ).bind( "propertychange._change" , function (event) { if (event.originalEvent.propertyName === "checked" ) { this ._just_changed = true ; } }); $( this ).bind( "click._change" , function (event) { if ( this ._just_changed && !event.isTrigger) { this ._just_changed = false ; } // Allow triggered, simulated change events (#11500) simulate( "change" , this , event); }); } return false ; } // Delegated event; lazy-add a change handler on descendant inputs $( this ).bind( "beforeactivate._change" , function (e) { var elem = e.target; if (rformElems.test(elem.nodeName) && !$._data(elem, "_change_attached" )) { $(elem).bind( "change._change" , function (event) { if ( this .parentNode && !event.isSimulated && !event.isTrigger) { simulate( "change" , this .parentNode, event); } $._data(elem, "_change_attached" , true ); }) } }); }, handle: function (event) { var elem = event.target; // Swallow native change events from checkbox/radio, we already triggered them above if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox" )) { return event.handleObj.handler.apply( this , arguments); } }, teardown: function () { facade.remove( this , "._change" ); return !rformElems.test( this .nodeName); } } special.submit = { setup: function () { // Only need this for delegated form submit events if ( this .tagName === "FORM" ) { return false ; } // Lazy-add a submit handler when a descendant form may potentially be submitted $( this ).bind( "click._submit keypress._submit" , function (e) { // Node name check avoids a VML-related crash in IE (#9807) var elem = e.target, form = /input|button/i.test(elem.tagName) ? elem.form : undefined; if (form && !$._data(form, "_submit_attached" )) { facade.bind(form, { type: "submit._submit" , callback: function (event) { event._submit_bubble = true ; } }); $._data(form, "_submit_attached" , true ); } }); // return undefined since we don't need an event listener }, postDispatch: function (event) { // If form was submitted by the user, bubble the event up the tree if (event._submit_bubble) { delete event._submit_bubble; if ( this .parentNode && !event.isTrigger) { simulate( "submit" , this .parentNode, event); } } }, teardown: function () { if ( this .tagName == "FORM" ) { return false ; } facade.remove( this , "._submit" ); } } return $; }) |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
2010-01-09 IE中不可遍历的属性
2010-01-09 访问私有变量