打印

SD9032: IE6 IE7 IE8 IE9(Q) 中表单元素常见事件不产生事件冒泡

作者:钱宝坤

标准参考

表单元素常用事件有 change、select、submit、reset,他们在 Document Object Model Events 规范中均被标注为可冒泡(Bubbles: Yes)。

问题描述

IE6 IE7 IE8 IE9(Q) 中 change、select、submit、reset 事件均不产生事件冒泡。

造成的影响

如果将事件处理委托给产生这些事件的父元素或祖先元素处理,在 Chrome Safari Firefox 中均是没有问题的。但是由于 IE6 IE7 IE8 中这些事件不产生事件冒泡,将会导致位于祖先元素的事件委托没有被执行,可能会导致错误数据提交或页面UI不正常等情况出现。

受影响的浏览器

IE6 IE7 IE8 IE9(Q)  

问题分析

最早的 DOM Level 2 Event Model 版本为 1999 年 3 月成文,变更至今日均指定了 change、select、submit、reset 事件均可产生事件冒泡:

select
The select event occurs when a user selects some text in a text field. This event is valid for INPUT and TEXTAREA elements.
  • Bubbles: Yes
  • Cancelable: No
  • Context Info: None
change
The change event occurs when a control loses the input focus and its value has been modified since gaining focus. This event is valid for INPUT, SELECT, and TEXTAREA. element.
  • Bubbles: Yes
  • Cancelable: No
  • Context Info: None
submit
The submit event occurs when a form is submitted. This event only applies to the FORM element.
  • Bubbles: Yes
  • Cancelable: Yes
  • Context Info: None
reset
The reset event occurs when a form is reset. This event only applies to the FORM element.
  • Bubbles: Yes
  • Cancelable: No
  • Context Info: None

规范的成文时间已经涵盖了 IE6 开发时间,因此可以基本推断早期的 IE 版本如果遵循了此规范,那么事件将会冒泡到祖先元素上。

事实是否如此呢?我们看一组测试用例:

<!DOCTYPE html> <html> <head> <script> window.onload = function() { var
                addEvent = (document.addEventListener) ? (function(el, type, fn) { el.addEventListener(type, fn, false);
                }) : (function(el, type, fn) { el.attachEvent('on' + type, fn) }); var stopDefault = function(e) {
                (window.event) ? window.event.returnValue = false : e.preventDefault(); }; var output = function (msg) {
                pElement.innerHTML += msg + '<br />'; }; var addOutputMessageByTargets = function(targets,
                targetNames, targetEventNames) { for (var i = 0, c = targets.length; i < c; ++i) { for (var j = 0,
                len = targetEventNames.length; j < len; ++j) { addEvent(targets[i], targetEventNames[j],
                (function(targetName, eventName) { return function(event) { if (targetName === 'HTMLFormElement'
                && eventName === 'submit') { stopDefault(event); } output(targetName + ' triggered ' + eventName
                + ' event.'); } })(targetNames[i], targetEventNames[j]) ); } } }; var pElement =
                document.getElementsByTagName('p')[0]; var divElement = document.getElementsByTagName('div')[0]; var
                formElement = document.getElementsByTagName('form')[0]; var inputTextElement =
                document.getElementsByTagName('input')[0]; var inputCheckboxElement =
                document.getElementsByTagName('input')[1]; var inputRadioElement =
                document.getElementsByTagName('input')[2]; var selectElement =
                document.getElementsByTagName('select')[0]; var textareaElement =
                document.getElementsByTagName('textarea')[0]; var clearMessageElement =
                document.getElementsByTagName('button')[0]; addOutputMessageByTargets( [window, document, document.body,
                divElement], ['DOMWindow', 'Document', 'HTMLBodyElement', 'HTMLDivElement'], ['submit', 'reset',
                'change', 'select'] ); addOutputMessageByTargets( [formElement], ['HTMLFormElement'], ['submit',
                'reset'] ); addOutputMessageByTargets( [ inputTextElement, inputCheckboxElement, inputRadioElement,
                selectElement, textareaElement ], [ 'HTMLInputElement type is text', 'HTMLInputElement type is
                checkbox', 'HTMLInputElement type is radio', 'HTMLSelectElement', 'HTMLTextareaElement' ], ['change'] );
                addOutputMessageByTargets( [inputTextElement, selectElement, textareaElement], [ 'HTMLInputElement type
                is text', 'HTMLSelectElement', 'HTMLTextareaElement' ], ['select'] ); addEvent(clearMessageElement,
                'click', function() { pElement.innerHTML = ''; }) }; </script> </head> <body>
                <div> <h3>Place change From: </h3> <form> <input type="text"/>
                <br /> <input type="checkbox" /> <br /> <input type="radio"
                name="radio"/> <br /> <select> <option>1</option>
                <option>2</option> </select> <br /> <textarea></textarea> <br
                /> <input type="submit" value="submit" /> <input type="reset"
                value="reset"/> </form> </div> <h3>output message: </h3>
                <p></p> <button>clear message</button> </body> </html>

用例中,我们将这些表单事件依次委托绑定给他们的父元素 DIV、祖先元素 BODY、以及位于事件冒泡顶层的 window 与 document 对象。

如果事件可冒泡,则我们将看到 DIV、BODY、document 与 window 均会在输出事件触发信息。反之,则可看到,仅触发事件的元素自身发出了事件消息。同时,还可以根据消息输出判断出是否有事件没有按照 DIV、BODY、Document、Window 的轨迹向上冒泡执行。

各浏览器中事件冒泡表现如下:

  IE6 IE7 IE8 IE9(Q)1 IE9(S)2 Firefox Chrome Safari Opera
change 事件 不冒泡 可冒泡至 window
select 事件 不冒泡 可冒泡至 window
submit 事件 不冒泡 可冒泡至 window
reset 事件 不冒泡 可冒泡至 window

【注1】:IE9(Q) 基本上是模拟 IE5.5 的整体表现方式,因此同样没有遵循规范中指定的事件冒泡规则。

【注2】:IE10 平台预览版第二版的标准文档模式与 IE9(S) 表现一致,事件均可冒泡,由于此篇成文时为非正式版本,故仅做提示而不入上表。

如上表所示,IE6 IE7 IE8 IE9(Q) 版本浏览器中 change、select、submit、reset 事件均不产生事件冒泡,导致其冒泡路径上的事件委托均没有被正常执行。

此现象说明 IE6 IE7 IE8 IE9(Q) 的 change、select、submit、reset 事件事实上都没有参照规范定义产生事件冒泡。

【注】:如果仅从直觉上来判断 IE6-8 的此部分处理是符合使用者预期的,这些事件应如同 focus、blur 事件一样不产生冒泡更合理。但是,考虑到可以触发这些事件的元素基本上都不可以被嵌套,那么规范如此定义将会为事件处理带来更大的灵活性。

解决方案

为了兼容低版本的 IE 浏览器,建议 change、select、submit、reset 事件均不要依赖事件冒泡机制委托给其祖先元素处理。

参见

知识库

相关问题

测试环境

操作系统版本: Windows 7 Ultimate build 7600
浏览器版本: IE6
IE7
IE8
IE9
Firefox 6.0
Chrome 16.0.891.0 dev-m
Safari 5.1(7534.50)
Operea 11.51
测试页面: form_elements_event_bubbles_test.html
本文更新时间: 2011-09-27

关键字

change select submit reset input textarea form 表单 表单元素 事件委托 事件冒泡