探索IE11下Dojo事件处理遇到的一些“怪象”

事件响应为 Web 应用提供了良好的交互式体验支撑,事件,即抓取某个人们关心的 UI 变化、键盘操作、鼠标操作等,当该事件被浏览器发出时,编程人员可以捕获该事件然后做一些特定的合乎逻辑的事情,让用户从这种舒适的交互中感到 Web 应用的"人性化",但是,事件编程也给开发者带来一些困扰,其中,最主要的就是跨浏览器对 DOM 实现的差异性,尤其是 IE, 尽管 IE11 的问世,已经将这种差异最小化,但是一些难以用程序逻辑来判断和解释的"怪象"仍然发生在 IE11 上,本文就几个遇到的问题进行探索和研究,帮助开发者在类似的问题中提出解决方案。

事件处理程序

事件,就是文档中发生的一些特定的交互的瞬间,它是用户或者用户代理执行的某种动作如 click、focus、load 等等。可以用事件处理程序来响应某个事件,这样,就可以在特定事件发生时做一些特定的事情。所谓事件处理程序,用一句话来概括:就是当特定事件发生时指定执行的特定代码。

在具体介绍事件处理程序之前,还需要介绍一个概念:事件流。事件流描述的是从页面接收事件的顺序,DOM2 级事件规定事件流包括 3 个阶段:事件捕获阶段,目标阶段,和事件冒泡阶段。也就是说,发生一个事件时,页面的顶层节点最先接收到它,然后逐级往下传播,最后到达发生事件的目标节点,事件并没有终止传播,而是接着向上层节点冒泡直至达到顶层节点。在 DOM 的事件流中规定目标不在捕获阶段接收到事件,目标阶段被包含进冒泡阶段。但多数浏览器都支持在两个阶段获取事件。

在 DOM0 级中指定事件处理程序的方式就是将一个函数(即事件处理方法)赋值给一个事件处理程序属性。每个元素都有自己的事件处理程序属性,如 window.onclick。使用 DOM0 级方法指定的事件处理程序被认为是元素的方法,因此,这时候事件处理程序在元素的作用域中运行,即程序中的 this 指向注册事件处理程序的元素。这里,特意指明是注册事件处理程序的元素,而不是触发事件的目标元素,在很多情况下,这二者相同。但是,有些情况下,它们是不同的。一个典型的例子就是事件委派(文章后部分将会提到),当把事件处理程序注册到更高节点层次上时,注册事件处理程序的元素往往是触发事件的元素的父节点。因此,要注意这样的差别。

清单 1. DOM0 级事件处理程序指定

var button = document.getElementById("myButton");button.onclick = function () { //this 指向元素 button,因此 alert 输出为 myButton alert( this.id );};// 只需要将 onclick 属性值置空即可删除事件处理程序button.onclick = null;

DOM0 级只是简单的将事件处理程序赋值给特定的节点事件处理程序属性。在 DOM2 级事件处理程序定义中,指定了两个方法用于指定和删除事件处理程序:addEventListener() 和 removeEventListener(),这两个方法都接受 3 个参数,要处理的事件,事件处理函数和一个布尔值。这个布尔值参数为 true 表示在事件捕获阶段调用事件处理程序,false 则表示在事件冒泡阶段调用事件处理程序。

清单 2. DOM2 级事件处理程序指定

var button = document.getElementById("myButton");var handler = function () { alert( this.id );};button.addEventListener( "click", handler, false);// 省略其他代码//移除处理程序button.removeEventListener( "click", handler, false);

使用 DOM2 级方法添加事件处理程序的好处是可以同时添加多个事件处理程序。而 DOM0 级事件处理程序属性每次只能指定一个函数,如果同时指定多个处理函数,则只有最后一个值会生效,就像定义变量那样,前面的值被新的值覆盖。 IE9 以前没有实现 DOM2 级标准定义的方法,而是实现了与 DOM 中类似的两个方法:attachEvent() 和 detachEvent()。这两个方法接受两个参数,事件处理程序名和事件处理函数。 在 IE 中可以使用 DOM0 级定义的事件处理程序指定方法,也可以使用 attachEvent(),它与使用 DOM0 级方法的主要区别在于事件处理程序的作用域。使用 attachEvent()方法注册的事件处理程序会在全局作用域中运行,即 this == window。

清单 3. IE 事件处理程序指定

var button = document.getElementById("myButton");var handler = function () { alert( this == window ); //true};button.attachEvent( "onclick", handler);button.detachEvent( "onclick", handler);

以上介绍的是 DOM0 级事件处理程序指定方法、DOM2 级事件处理程序指定方法以及 IE9 以前的事件处理程序指定方法,不同的浏览器对 DOM 的实现情况不同,事件处理程序指定方法也不同,甚至 event 对象也有两种,这给开发者在跨浏览器编程中带来麻烦,在下一章节我们会简单介绍跨浏览器的事件处理程序指定方法的 Dojo 实现,即屏蔽了各个浏览器差异性的事件处理程序指定的封装。这样使得事件绑定变得更加容易。

跨浏览器事件处理程序的 Dojo 接口

虽然 DOM0 级和 DOM2 级标准已经提供了一套机制来注册事件处理程序,但如前面所说,并不是所有浏览器都遵循了 W3C DOM 标准,如 IE,也不是所有的浏览器都支持同一级的 DOM 标准,在这些各式各样对 DOM 实现的主流浏览器中,有三种方式来注册事件处理器,它们分别是: DOM 0、addEventListener 以及 attachEvent 。除此之外,IE9 以前还有不同于 DOM 事件的 event 实现,并且,在移除注册了事件处理器的节点的之前,要先清除与其绑定的所有事件处理函数,否则,IE 的内存清理机制会导致这些节点和它们的事件处理函数仍然占用内存导致内存无法释放。 Dojo 通过封装不同浏览器实现的 API 之间的差异从而简化了同 DOM 事件交互的复杂度,并且能够有效的防止内存泄露。而这种跨浏览器的事件处理程序指定都是通过 Dojo/on 这个简单、直接的事件接口来实现的。dojo/on module 提供了一套接口,使得 DOM 事件可以被轻松响应。下面通过清单 4 和清单 5 简单演示 dojo/on 模块的使用。

清单 4. 简单的 HTML 标签

<button id="btnTryIt">Click me!</button><div id="divShowIt" style="visible:hidden">Hello,Welcome!</div>

清单 5. Dojo 事件捕获响应

require(["dojo/on", "dojo/dom", "dojo/dom-style", "dojo/domReady!"],function(on, dom, domStyle) { var button = dom.byId("btnTryIt"), div = dom.byId("divShowIt"); on(button, "click", function(evt){ domStyle.set(div, "visible", "block"); });});

和之前所述的众接口很像,Dojo 也提供了专门的方法来移除特定的事件处理程序: handle.remove。调用 on 接口为事件绑定处理程序时会返回一个包含 remove 方法的对象的返回值,其内部封装了各种浏览器对于移除事件处理程序的差异性,调用 remove 方法可以轻松移除事件处理程序。 同 DOM0 级注册事件处理程序运行的作用域一样,on 会将注册的事件处理程序运行在注册元素的上下文中,但是在 Dojo 中,可以使用 dojo/_base/lang 模块的 lang.hitch 接口轻松改变事件处理程序的作用域,如清单 6 中的代码所示:

清单 6. 改变事件处理程序的上下文

require(["dojo/on", "dojo/dom", "dojo/_base/lang", "dojo/domReady!"],function(on, dom, lang) { var myScopedButton1 = dom.byId("myScopedButton1"), myScopedButton2 = dom.byId("myScopedButton2"), myObject = { id: "myObject", onClick: function(evt){ alert("The scope of this handler is " + this.id); }};// This will alert "myScopedButton1"on(myScopedButton1, "click", myObject.onClick);// This will alert "myObject" rather than "myScopedButton2"on(myScopedButton2, "click", lang.hitch(myObject, "onClick"));});

虽然事件处理程序为 Web 应用提供了强有力的交互力,但是在页面中滥用事件处理程序会直接造成大量的内存占用以及更多次数的 DOM 节点访问从而引起运行性能损失。"事件委派"可以很好的解决这个问题。所谓"事件委派"就是把事件处理委托给更高级别的节点,这个方法的核心思想是注册一个单一的事件处理程序到一个更高的节点级别,利用事件的冒泡机制,在事件冒泡到注册了事件处理程序的父节点阶段来响应事件。在这种情况下,首先会检查事件的目标,看它是否是从需要被处理的子节点冒泡得到的,如果是这样,事假处理程序的代码逻辑才会被调用执行。 为了更好的阐述这一思想,可以参考清单 7 中的例子:

清单 7. 事件委派

<ul id="myList"><li id="apple">apple</li><li id="orange">orange</li><li id="stawberry">stawberry</li><li id="pineapple">pineapple</li></ul><script>require(["dojo/on", "dojo/dom", "dojo/query", "dojo/domReady!"],function(on, dom){ var myObject = { id: "myObject", onClick: function(evt){ switch(this.id){ case "apple": alert("You want apple!"); break; case "orange": alert("You want orange!"); break; default: alert("You want stawberry or pineapple!"); } } }; var list = dom.byId("myList"); on(list, "click", myObject.onClick);});</script>

当运行以上代码的时候,请注意 this 指向触发事件的目标节点,而不是注册了事件处理器的高层次节点。这是一个重要的区别,当使用"事件委派"来代替为单一事件注册事件处理程序时,this, 不再指向传入的第一个参数节点,而是与选择器匹配的那个触发了事件的目标节点。

前面所述的事件连接都是建立在已知节点,这里将它称为目标导向型。当我们没有为创建事件处理程序或者编程时不知道节点是否已经创建时,这样的事件连接我称为主题导向型(或者事件导向型),就需要用到 Dojo 的 publish 和 subscribe 框架,它们定义在 dojo/topic 模块中。这种框架只需要关心主题本身。当特定主题被发布时,订阅该主题的订阅者就会收到消息并触发处理器调用处理逻辑。如果订阅者想终止订阅某个主题,topic.subscribe 同样返回一个带有 remove 方法的对象。调用这个方法即可移除响应的处理器。

综上所述,Dojo 的事件响应系统十分强大,并且简单易用。dojo/on 屏蔽了浏览器之间对 DOM 事件的不同实现。 publish/subscribe 框架,dojo/topic 使得开发者轻易将事件生产者和消费者松耦合。在简单了解了 Dojo 对注册事件处理程序的封装后,我们简单了解一下事件对象和类型。并且重点介绍一下 IE11 上遇到"怪象"的事件。

事件对象和类型

DOM 事件对象

任何实现了 DOM 的浏览器,在指定了事件处理程序的事件被触发时,都会将一个 event 事件对象作为参数传入事件处理程序中。event 对象包含了与创建它的特定事件有关的属性和方法。在事件处理程序内部,对象 this 等于 event.currentTarget,即指派了事件处理程序的当前元素,这个 event.currentTarget 事件当前目标就是在前面提到过的注册事件处理程序的作用域。而 event.target 事件目标只包含事件的实际发生目标,所以,当事件处理程序注册在目标节点 B 的父节点 A 上,则 event.currentTarget 等于 A ,而 event.target 等于 B。除此之外,每个事件可能会有其特定的默认行为,如点击一个链接,默认行为是跳转到链接所指向的页面。要阻止特定事件的默认行为,可以使用 event.preventDefault()方法。注意,这个方法只在 event.cancelable 属性值为 true 的时候才能生效。另外,可以通过调用 event.stopPropagation()方法来阻止事件冒泡。

IE 事件对象(未实现 DOM2 级的 IE9 以前)

与访问 DOM 中的 event 对象不同,IE(IE9 以前)的 event 对象有几种不同 的访问方式,这取决于事件处理程序的指定方式是 DOM0 级事件定义的方法(为事件处理程序属性赋值)还是使用 IE 自身实现的 attachEvent()。在使用 DOM0 级方法添加事件处理程序时,通过访问 window 对象的属性 window.event 来获取 event 对象,而使用 attachEvent()方法时,虽然也可以通过 window.event 来获取 event 对象,但 event 对象本身可以通过参数传递进来,所以为了方便,可以在程序中直接使用该参数。 在 Internet Developer Center 的 IE9 开发文档描述上,我们看到,从 IE9 开始的新版本遵从了 W3C 事件标准,实现了 DOM2 级和 DOM3 级事件,支持了包括事件注册和事件模拟的各个 API(addEventListener, removeEventListener, createEvent 和 dispatchEvent),新增了事件对象如键盘事件 KeyboardEvent、鼠标事件 MouseEvent 等,并且新增了基于标准的事件类型。使得开发者再也不必在事件编程时为 IE 专门创建独立的分支,使用 IE 特有的 attchEvent()方法等等,这不仅减少了代码维护量,也使得各个浏览器之间事件处理的差异性变小。

Web 事件类型

在网络应用中,事件类型繁多,如前面叙述,特定的事件有特定的属性和方法,DOM3 级将事件归类为以下几种:

UI Event 用户界面事件 Focus Event 焦点事件 Mouse Wheel Event 滚轮事件 Text Event 文本事件 Keyboard Event 键盘事件 Composition Event 合成事件 Mutation Event 变动事件 变动名称事件(已废弃)

焦点事件是本文介绍的重点,在 IE11 中出现最多的"怪象"。焦点事件会在页面元素失去焦点时触发,主要包含以下 6 个事件:

DOMFocusIn:在元素获得焦点时触发,已被 DOM3 废弃,选择 focusin。 DOMFocusOut:在元素失去焦点时触发,已被 DOM3 废弃,选择 focusout。 blur:在元素失去焦点时触发,该事件不会冒泡。 Focus:在元素获得焦点时触发,该事件不会冒泡。 focusin:在元素获得焦点时触发,冒泡。 focusout:在元素失去焦点时触发,冒泡。

这几个事件中,focus 和 blur 是得到所有浏览器支持的事件,但这些事件的缺点是不冒泡,因此,IE 的 focusin 和 focusout 与 Opera 的 DOMFocusIn 和 DOMFocusOut 发生重叠,最后 DOM3 级标准采纳了 IE 的 focus 事件方式。 当焦点从页面的一个元素 A 移动到另一个元素 B,即 A 失去焦点,B 获得焦点,会依次触发下列事件:

focusout 在 A 元素上触发。 focusin 在 B 元素上触发。 blur 在 A 元素上触发。 DOMFocusOut 在 A 元素上触发。 focus 在 B 元素上触发。 DOMFocusIn 在 B 元素上触发。

其中,focusout,blur 和 DOMFocusOut 的事件目标是失去焦点的元素 A,而 focusin,focus 和 DOMFocusIn 的事件目标是获得焦点的元素 B。要确定用户代理是否支持这些 DOM3 级焦点事件,可以使用以下代码来检测:

var isFocusEventSupported = document.implemention.hasFeature("FocusEvent","3.0");

注意,DOM2 级焦点事件命名为复数形式 FocusEvents,而 DOM3 级里变成了单数形式 FocusEvent。

DOM 规范没有涵盖所有浏览器支持的所有事件,很多用户代理出于各自支持功能的需要,还实现了很多自定义的事件。这里为了阐述将要罗列 IE11 中的某个事件"怪象",在这里,还需要了解触摸与手势事件。有一些设备提供了触摸功能,例如 iPhone、iPad。iOS 提供了一些与触摸(touch)和手势(gesture)相关的事件。后来,在 Android 的浏览器上也实现了相同的事件。触摸事件在用户手指接触屏幕时、在屏幕上滑动时或者从屏幕上离开时触发。具体来说有一下几个事件:

touchstart:手指触摸屏幕时触发。 touchmove:手指在屏幕上滑动时连续触发。 touchend:手指从屏幕移开时触发。 touchcancel:设备停止跟踪触摸时触发。

前面所述都是一个手指触摸屏幕时触发的事件,当有两个手指触摸屏幕时就会产生手势,手势事件有如下 3 种:

gesturestart: 当一个手指按在屏幕上而另一个手指也触摸屏幕时触发。 gesturechange:当接触屏幕的任意手指位置发生移动时触发。 gestureend:当任意手指离开屏幕时触发。

IE 中没有提供这一套事件,与之类似,从 IE10 开始,提供了 Pointer 事件、Gesture 事件、Manipulation 事件以及 CSS 属性等一系列方式来提供 touch 支持。从 win8 开始,IE 支持了 W3C Pointer Events,用于处理用户输入。Pointer 事件继承自 MouseEvent,并且通过类似的用户交互触发。但是在 IE10 发布时并没有实现 W3C 事件标准,使用的是带有厂商 MS 前缀的特有的事件名称,如 MSPointerDown、MSPointerMove 等,IE11 遵从了 W3C 标准,将这一前缀去掉,与鼠标事件对应的 Pointer 事件如表 1 所示:

表 1. 与鼠标事件对应的 Pointer 事件

Mouse event Corresponding pointer event mousedown pointerdown mouseenter pointerenter mouseleave pointerleave mousemove pointermove mouseout pointerout mouseover pointerover mouseup pointerup

如果需要在代码中使用 Pointer 事件,为了维护代码与 IE10 的兼容性,可以使用如清单 8 所示的代码:

清单 8. 与 IE10 兼容的 Pointer 事件

var btn = document.getElementById("button1");if(window.PointerEvent){ btn.addEventListener("pointerdown", func);} else if (window.MSPointerEvent) { btn.addEventListener("MSPointerDown", func);} else{ btn.addEventListener("mousedown", func);}

PointEvent 对象提供了所有在鼠标事件中已有的通用属性,以及额外的可以帮助开发者区分输入类型和字符的属性。在发生了 Pointer 事件之后,IE 还会为主接触触发鼠标事件,它使得基于鼠标事件的网站可以正常运行。而与鼠标事件不同,Pointer 事件支持 multi-touch。有些情况下,可能会在屏幕上有多个触点,每个触点都会触发一个 Pointer 事件。 下面,针对 IE11 上,Dojo 事件处理发生的一些"怪象"进行深入研究。

IE11 下 Dojo 事件处理遇到的问题

focus()调用导致的死循环

Dojo 提供了一个名为 RichText 的 dijit 小部件供我们创建富文本应用。每次加载一个 RichText 时,它都会调用一个预定义的名为_loadFunc()的函数。如清单 9 所示:

清单 9. RichText.js onload 事件处理程序指定

// Onload handler fills in real editor content.// On IE9, sometimes onload is called twice, and the first time//frameElement is null (test_FullScreen.html)"onload='frameElement && frameElement._loadFunc(window,document)' ","style='" + userStyle + "'>", html, "</body>\n</html>"//其他代码省略

而在_loadFunc 函数内部又调用了 this.onLoad(html),如清单 10 所示:

ifr._loadFunc = lang.hitch(this, function(w){ this.window = w; this.document = w.document; // instantiate class to access selected text in editor's iframe this.selection = new selectionapi.SelectionManager(w); if(has("ie")){ this._localizeEditorCommands(); } // Do final setup and set initial contents of editor this.onLoad(html);});

可以看到,在 onLoad() 方法中,为了解决 iframe 里通过 tab 键无法 focus 到 RichText 文本的问题,添加了如下的代码,如清单 11 所示:

清单 11. Iframe focus 事件转移

onLoad: function(/*String*/ html){ // summary: // Handler after the iframe finishes loading. // html: String // Editor contents should be set to this value // tags: // protected // TODO: rename this to _onLoad, make empty public onLoad() method, // deprecate/make protected onLoadDeferred handler? if(!this.window.__registeredWindow){ this.window.__registeredWindow = true; this._iframeRegHandle = focus.registerIframe(this.iframe); } // there's a wrapper div around the content, see // _getIframeDocTxt(). this.editNode = this.document.body.firstChild; var _this = this; // Helper code so IE and FF skip over focusing on the <iframe> //and just focus on the inner <div>. // See #4996 IE wants to focus the BODY tag. this.beforeIframeNode = domConstruct.place("<div tabIndex=-1></div>", this.iframe, "before"); this.afterIframeNode = domConstruct.place("<div tabIndex=-1></div>", this.iframe, "after"); this.iframe.onfocus = this.document.onfocus = function(){ _this.editNode.focus();};

项目中创建一个包含多个 RichText 的子窗口时,正常情况下打开这个子窗口,不会触发_this.editNode.focus()执行,只有当使用鼠标或者键盘触发 iframe 的 focus 事件时,iframe 的 onfocus 事件处理程序才会执行该行代码。而在 IE11 上,首次打开子窗口就会调用 this.editNode.focus() 这行代码,并且程序陷入死循环,高达百分之九十几的 CPU 占用率最终导致 IE 僵死。仔细检查上下代码逻辑,没有发现问题,并且在 Chrome,Firefox 以及 IE9,IE10 等浏览器上运行良好。在 IE11 上,一次_this.editNode.focus() 执行完毕后,this.iframe,onfocus 事件处理程序自动的重复被触发,从而导致代码进入死循环。

现在注释掉改行代码_this.editNode.focus() ,加上一个 alert 语句,如清单 12 所示。发现 iframe 并没有不断的触发 focus 事件。但它的行为也不同于其他浏览器:在 Chrome 或者 Firefox 等其他浏览器上,没有鼠标操作或者 tab 键 focus 到 RichText 上时,iframe.onfocus 事件处理程序并不会被触发,只有 focus 到对应的 RichText 上,iframe 才会 focus 从而触发事件处理程序执行。这是符合开发者期望的正常状态。在 IE11 上,没有鼠标或是键盘触发,在初始化 RichText 的时候,this.iframe.onfocus 事件处理程序就会自动被触发执行。也就是说假如子窗口上有 3 个 RichText,this.iframe.onfocus 就会被触发 3 次,内部的 alert 语句会执行 3 次。

清单 12. 为 onfocus 事件处理程序 debug

his.iframe.onfocus = this.document.onfocus = function(){ //_this.editNode.focus(); alert("go into this part");};

也就是说问题有两个,第一个,为什么 RichText 在初始化时就会触发 iframe 的 onfocus 事件处理程序;第二个,_this.editNode。focus()为什么会陷入循环调用。问题原因尚不能确定,Dojo 上可以找到已经 fix 的极为类似的 defect#16939(IE hangs when switching between multiple editors / refocusing browser window):

Steps to reproduce:1. Open: ​http://download.dojotoolkit.org/release-1.8.3/dojo-release-1.8.3/\ dijit/tests/editor/test_Editor.html2. Click editor 'No plugin, initially empty'3. Click editor 'Created from div'4. Click Desktop or other app (so editor loose focus)5. Repeat steps 2-4 several timesResult: IE hangs.At the point IE hangs, both of editors endlessly receive a lot of 'focus' and 'blur' events. Reproducible:Browsers: IE9 and IE10OS: Win 7 x64 (host machine, 4 processors), Win 8 x32 (VM, 2 processors)

其修改历史如图 1 所示:

图 1. defect #16939 RichText.js 修改历史

但是上述修改对 IE11 并不生效,Dojo 提供了 dijit/focus 这个功能模块用于管理页面上可聚焦的节点和 widgets。为了表述方便,后文中提到的 focusUtil 指代 dijit/focus。这里,我们可以调用 focusUtil.curNode 来观察获取焦点的节点。以及使用 focusUtil.watch()来观察焦点的变化。

在 Dojo 的 dijit/focus.js 文件中,IE9+浏览器中,当把焦点从一个 iframe 的 editor(RichText)移到一个普通的 dom 节点上时,在 focusout 事件时候会紧跟一个 focusin 事件,代码中定义了一个时间变量 lastFocusin,当每次调用 focusin 事件处理程序时记录调用时间,在调用 focusout 事件处理程序之前,先判断当前时间值与 lastFocusin 时间值之差是否大于 100,大于才执行代码逻辑,否则返回。这样有效的阻止了刚刚描述的问题。如清单 13 所示:

清单 13. 阻止 focusout 事件后紧跟 focusin 事件

var fih = on(body, 'focusin', function(evt){ lastFocusin = (new Date()).getTime(); // When you refocus the browser window, IE gives an event // with an empty srcElement if(!evt.target.tagName) { return; } // IE reports that nodes like <body> have gotten focus, //even though they have tabIndex=-1, // ignore those events var tag = evt.target.tagName.toLowerCase(); if(tag == "#document" || tag == "body"){ return; } if(a11y.isFocusable(evt.target)){ _this._onFocusNode(effectiveNode || evt.target); }else{ // Previous code called _onTouchNode() for any // activate event on a non-focusable node. Can // probably just ignore such an event as it will be //handled by onmousedown handler above, but // leaving the code for now. _this._onTouchNode(effectiveNode || evt.target); } }); var foh = on(body, 'focusout', function(evt){ // IE9+ has a problem where focusout events come //after the corresponding focusin event. At least // when moving focus from the Editor's <iframe> //to a normal DOMNode. if((new Date()).getTime() < lastFocusin + 100){ return; } _this._onBlurNode(effectiveNode || evt.target);});

本文作者实际上也采用了这种 timer 的方式来阻止连续的 focus()调用,即在代码中放置一个计时器,用于判断两次 focus()调用时间差,如果这个时间差大于指定的值,才执行代码,否则跳过。如清单 14 所示:

清单 14. Timer 计时器阻止连续的 focus()调用

this.iframe.onfocus = this.document.onfocus = function(){ // IE11 has a problem where focus events between multi richtexts will //lead to the focus events go into infinite loop. var isIE11 = navigator.userAgent.indexOf("Trident/7.0") !== -1; if(isIE11){ if((new Date()).getTime() < lastFocusTimeStamp + 100){return;} lastFocusTimeStamp = (new Date()).getTime(); } _this.editNode.focus();};

注册 touch 事件引起的反常捕获

前文介绍过 iOS 为触摸支持专门新增的一系列 touch 事件:touchstart,touchend 等。而在 IE 中对应的是 Pointer 事件。在 Dojo 的 focus.js 文件中,会有一个注册窗口专门用于监听特定事件的方法:registerWin,其中就监听了 touchstart 事件,如清单 15 所示:

清单 15.为目标窗口注册监听事件

registerWin: function(/*Window?*/targetWindow,/*DomNode?*/effectiveNode){ // 省略多行注释 var _this = this, body = targetWindow.document && targetWindow.document.body; if(body){ var mdh = on(targetWindow.document, 'mousedown, touchstart', function(evt){ _this._justMouseDowned = true; setTimeout(function() { _this._justMouseDowned = false; }, 0); // workaround weird IE bug where the click is on an orphaned node // (first time clicking a Select/DropDownButton inside a // TooltipDialog). // actually, strangely this is happening on latest chrome too. if(evt && evt.target && evt.target.parentNode == null){ return; } _this._onTouchNode(effectiveNode || evt.target, "mouse"); }); //省略多行代码 }},

这段代码为目标窗口注册了 mousedown,touchstart 事件处理程序,试想,如果注册了特定浏览器不存在的事件,会发生什么样的事情?用一个最简单的例子,为目标窗口注册一个所有浏览器上根本不存在的事件:quirk event,观察一下,在 chrome,Firefox 和 IE 上分别会有什么样的反应?通过项目实验发现,在其他浏览器上,这种容错性很强,不会明显的影响用户体验。而在 IE8、9、10、11 上,除了 IE8 没有明显异常,其他几个都会引起程序崩溃。也就是说,IE 在对其不存在的事件进行注册监听,会发生意想不到的事情,而 IE11 虽然很大程序上区别于以往的版本,但是在 DOM 事件实现上仍需改进。如前所述,IE11 支持了标准的 W3C Pointer 事件来支持触屏,而如前面所述,较早版本的 IE10 则使用的是带有 MS 特定前缀的事件,如 MSPointerDown。所以,针对不同浏览器,对以上代码进行修饰,如清单 16 所示:

这段代码为目标窗口注册了 mousedown,touchstart 事件处理程序,试想,如果注册了特定浏览器不存在的事件,会发生什么样的事情?用一个最简单的例子,为目标窗口注册一个所有浏览器上根本不存在的事件:quirk event,观察一下,在 chrome,Firefox 和 IE 上分别会有什么样的反应?通过项目实验发现,在其他浏览器上,这种容错性很强,不会明显的影响用户体验。而在 IE8、9、10、11 上,除了 IE8 没有明显异常,其他几个都会引起程序崩溃。也就是说,IE 在对其不存在的事件进行注册监听,会发生意想不到的事情,而 IE11 虽然很大程序上区别于以往的版本,但是在 DOM 事件实现上仍需改进。如前所述,IE11 支持了标准的 W3C Pointer 事件来支持触屏,而如前面所述,较早版本的 IE10 则使用的是带有 MS 特定前缀的事件,如 MSPointerDown。所以,针对不同浏览器,对以上代码进行修饰,如清单 16 所示:

清单 16. 为浏览器区分注册 touch 事件

if(body){ //If add handler to event touchstart on IE10,it will be a quirk //execuating the code below repeatly. //IE10 and IE11 does not support the touchstart and touchend type of //events that used by iOS and other mobile browsers. var evtDef = "mousedown, touchstart"; var isIE11 = navigator.userAgent.indexOf("Trident/7.0") !== -1; if (isIE11){ //IE11 deprecated the prefix MS and use event name //pointerdown instead. evtDef = "mousedown, pointerdown"; }else if (has("ie")){ // IE10 or IE8/9 var isTouchEnabled = navigator.msPointerEnabled && (navigator.msMaxTouchPoints > 0); evtDef = isTouchEnabled ? "mousedown, MSPointerDown" : "mousedown"; } var mdh = on(targetWindow.document, evtDef, function(evt){ _this._justMouseDowned = true; setTimeout(function(){ _this._justMouseDowned = false; }, 0); // workaround weird IE bug where the click is on an orphaned node // (first time clicking a Select/DropDownButton inside a // TooltipDialog). // actually, strangely this is happening on latest chrome too. if(evt && evt.target && evt.target.parentNode == null){ return; } _this._onTouchNode(effectiveNode || evt.target, "mouse");});

在 Dojo 上也发生过类似的问题,defect#17648(IE9,10,11(?) Hangs in some cases while changing focus),描述 IE9 和 IE10 中,极少的情况下,当某些 widget 失去焦点的时候 ,会发生无限循环调用:

In rare cases IE9 and IE10 (not tested in IE11) drive to infinite recurse while some widget (TextArea in my case) loose a focus. After deep investigation, i'm found this recurse relay on added event listeners in dojo focusManager. Actually problem with touchstart event in code.

同上面 IE11 的问题一样,他们也观察到该问题与同样的代码有关。如图 2 所示:

图 2. defect #17648

与 defect#17648 类似,defect#17378 也在描述类似的问题:

When I use IE10 and dojo 1.9, I find out that After I open a dialog and close it, the document. activeElement is null. But on IE8 and FF, it is not null. If on IE10, the document. activeElement is not null, and I click any widgets to do some JS, the code "var mdh = on(targetWindow.document, 'mousedown, touchstart', function(evt){...});" which is in focus.js will be executed repeatedly. But on IE8 and FF, it won't. ,如图 3 所示。

由于问题发生的情况也是极少情况下,无法确定规律,所以被标记为 wontfix。

图 3. Defect #17378

在这里就不详细叙述,读者可以参考本文的参考资源中给出的链接了解更详细的内容。

综上所述,IE11 可能发生的问题五花八门,有些问题甚至难以检测原因。但是,我们可以在编程时,有效的避免这些奇怪问题发生的概率。常用的方法有能力检测,怪癖检测以及浏览器检测。其中首选能力检测。即在执行一段代码之前,应当先判断浏览器是否有这样的能力。这使得代码更为可靠。

时间: 2024-08-03 04:04:59

探索IE11下Dojo事件处理遇到的一些“怪象”的相关文章

网页-Ie11下页面中的flash不刷新问题

问题描述 Ie11下页面中的flash不刷新问题 网页页面中生成一个flash,其他浏览器按f5刷新页面之后 flash的内容每次都会改变 ,但在IE11里边按f5刷新 flash的内容不会有任何变化,正常来说每次刷新页面,应该都会到服务器端做新的请求,都会重新输出这段代码,但是在ie11里就是不刷新 .这是什么情况 生成flash的源码: var so = new SWFObject("http://www.mytest.com/Widgets/CarouselV1.swf", &q

extjs可输入下拉框在IE11下的问题

问题描述 extjs可输入下拉框在IE11下的问题 airportFrom=Ext.create('Ext.form.ComboBox'{ renderTo:tid+'airportFrom' width:100 store:airportStore displayField:'airptChnNm' valueField:'airptCd' mode:'local' enableKeyEvents:true triggerAction:'all' minChars:1//用户必须自动完成输入之

ie 11-CKEditor在ie11下文字输入过长,拖动下面滚动条不好使

问题描述 CKEditor在ie11下文字输入过长,拖动下面滚动条不好使 好久没来CSDN了,CKEditor在ie11下文字输入过长,拖动下面滚动条不好使,拖动的时候还时不时会选中框内文字, 各位有没有类似问题,希望能帮助解决,谢谢啦! 解决方案 原来没有悬赏在哪里都是这么的悲催!哎,这几天总是灰蒙蒙的,终究还是走不出屌丝程序员的平凡世界 解决方案二: 版主也不出来帮忙了?快来啊大版主们o(><)o ~~o(><)o ~~o(>_<)o ~~

ie11-请问下CKEditor3.6在IE11下不能复制粘贴的问题如何解决

问题描述 请问下CKEditor3.6在IE11下不能复制粘贴的问题如何解决 1:今天发现我的Ckeditor在IE11下是不能粘贴的,请问大牛们,能解决么, 可以不升级解决么. 解决方案 需要找官方解决,看能否提供支持IE11版本 解决方案二: 只要 4.4 兼容 3.6,差别大也没有关系啊! 先在官网上查查吧,现在一般的软件都能兼容以前的版本的.或者提供兼容的方法! 解决方案三: 浏览器兼容问题. 不升级CKEditor应该解决不了.

https重定向http,在ie11下,http获取不到cookie?

问题描述 https重定向http,在ie11下,http获取不到cookie? 从https:xxx.ff.com到http:yyy.ff.com,在https写cookie,之后在http下,获取不到cookie(在ie6-10,火狐,谷歌都能获取), 只有IE11获取不到,网上说在写cookie的时候加上p3p,试了还是没用.求 大神帮忙解决一下. 解决方案 换一个方式,比如只有http或者https

ie 11-js 使用 Scriptx打印控件进行打印,在IE11下,会多出一个空白页

问题描述 js 使用 Scriptx打印控件进行打印,在IE11下,会多出一个空白页 js 使用 Scriptx打印控件进行打印,在IE11下,会多出一个空白页.求各位大神指点. 解决方案 如果确定有一个空白页,输出页面的时候少打印一页

Forms身份认证在IE11下无法保存Cookie的问题_实用技巧

ASP.NET中使用Forms身份认证常见的做法如下: 1. 网站根目录下的Web.config添加authentication节点 复制代码 代码如下: <authentication mode="Forms"> <forms name="MyAuth" loginUrl="manager/Login.aspx" defaultUrl="manager/default.aspx" protection=&q

_dopostback 在ie11下显示“未定义”

问题描述 按照网上讲的方法1打补丁下载后,在服务器上运行显示"被某些条件阻止",不能运行.2手动修改修改ie.browser,添加关于ie11的部分.netframework下的和netframework64下的都改过了,仍然显示错误修改单一站点下的ie.browser我是vs2010,修改后发布到服务器上,仍然显示错误还有什么方法吗 解决方案 解决方案二:ie.browser一共需要3个文件...你一个i文件是不行的你可以我我发给你就是了.解决方案三:太谢谢了3个文件用qq发吗私信给

求助 IE10和IE11下,某些ASP.NET 服务器控件生成失效问题

问题描述 这个问题确认是微软浏览器的bug网上给了两种修复的办法解决方法:1.下载微软的IE10补丁.NET4-http://support.microsoft.com/kb/2600088.NET2.0http://support.microsoft.com/kb/2600100forWin7SP1/WindowsServer2008R2SP1,WindowsVista/Server2008,WindowsXP/Server2003http://support.microsoft.com/kb