JavaScript: 如何在Internet Explorer中模拟change事件(委托)

14

更新:(概述、fiddle 和奖励)

这个问题没有得到太多关注,所以我要花一些声望来解决它。我知道我在回答和问题中往往过于冗长。这就是为什么我设置了这个fiddle,在我看来,它是目前我需要使用的代码的一个不错的表示。 我正在尝试解决以下几个问题:

  1. 伪更改事件不会在选择元素上触发,除非它失去焦点。 在某些情况下,客户端应在选择新值后重定向。 我该怎么做?
  2. 处理程序既在单击标签时被调用,也在单击复选框本身时被调用。 从本质上讲,这是您所期望的,但由于事件冒泡,不可能确定单击了哪个元素。 IE的事件对象没有realTarget属性。
  3. 在IE中通过单击标签更改复选框的checked-状态很顺利(尽管需要一些恶心的解决方法),但是直接单击复选框时,处理程序被调用,但是检查状态保持不变,直到我第二次单击。 然后值才改变,但处理程序没有被调用。
  4. 当我切换到其他选项卡,然后再切回来时,处理程序会被多次调用。 如果已经更改了选中状态,则调用三次,如果我只点击过一次 checbox,则调用两次。

任何可以帮助我解决上述一个或多个问题的信息都将不胜感激。 请注意,我没有忘记添加jQuery标签,我喜欢纯JS,因此正在寻找纯JS答案。


我有一个包含超过250个选择元素和20-30个复选框的网页。 我还必须跟踪用户的操作并采取相应措施。 因此,在我的看法中,委派更改事件而不是添加数百个侦听器是非常自然的。

当然,IE -公司策略:必须支持IE8-在我需要它时不会触发onchange事件。 因此,我正在尝试伪造onchange事件。 到目前为止,我所拥有的东西工作得相当好,除了有一件事情真的很烦人。
我使用onfocusinonfocusout来注册事件。 在某些情况下,当用户从选择元素中选择新值时,脚本应立即响应。 但是,只要select没有失去焦点,这将不会发生。

以下是我迄今为止想出的内容:

i = document.getElementById('content');
if (!i.addEventListener)
{
    i.attachEvent('onfocusin',(function(self)
    {
        return function(e)
        {
            e = e || window.event;
            var target = e.target || e.srcElement;
            switch (target.tagName.toLowerCase())
            {
                case 'input':
                    if (target.getAttribute('type') !== 'checkbox')
                    {
                        return true;
                    }
                    return changeDelegator.apply(self,[e]);//(as is
                case 'select':
                    self.attachEvent('onfocusout',(function(self,current)
                    {
                        return function(e)
                        {
                            e = e || window.event;
                            var target = e.target || e.srcElement;
                            if (target !== current)
                            {
                                return;
                            }
                            self.detachEvent('onfocusout',arguments.callee);//(remove closure
                            return changeDelegator.apply(self,[e]);
                        }
                    })(self,target));
                default: return;
            }
        }
    })(i));//(fake change event, buggy
}
else
{//(to put things into perspective: all major browsers:
    i.addEventListener('change',changeDelegator,false);
}

我尝试在onfocusin处理程序内部附加另一个事件监听器,绑定到onclick事件。它会触发当前拥有焦点的选择器的onfocusout事件。唯一的问题是,99.9%的用户会点击选择器,所以focusin事件也会触发一个onclick。

为了解决这个问题,我创建了闭包,并将当前的选择器和其原始值作为参数传递给它。但是一些用户确实使用键盘,这些用户经常切换到下一个选择框而不更改值。每次绑定一个新的onclick监听器...我确信必须有比检查所有e.type并单独处理它们的方法更简单的方法。
只是作为示例:具有额外onclick侦听器的代码:所有代码与第一个片段相同,因此我只粘贴case 'select':块。

                case 'select':
                    self.attachEvent('onfocusout',(function(self,current)
                    {
                        return function(e)
                        {
                            e = e || window.event;//(IE...
                            var target = e.target || e.srcElement;
                            if (!(target === current))
                            {
                                return;
                            }
                            self.detachEvent('onfocusout',arguments.callee);//(remove closure
                            return changeDelegator.apply(self,[e]);
                        };
                    })(self,target));
                    self.attachEvent('onclick',(function(self,current,oldVal)
                    {
                        return function(e)
                        {
                            e = e || window.event;
                            var target = e.target || e.srcElement;
                            if (target.tagName.toLowerCase() === 'option')
                            {
                                while(target.tagName.toLowerCase() !== 'select')
                                {
                                    target = target.parentNode;
                                }
                            }
                            if (target !== current)
                            {//focus lost, onfocusout triggered anyway:
                                self.detachEvent('onclick',arguments.callee);
                                return;
                            }
                            var val = target.options[target.selectedIndex].innerHTML;//works best in all browsers
                            if (oldVal !== target.options[target.selectedIndex].innerHTML)
                            {
                                self.detachEvent('onclick',arguments.callee);
                                return target.fireEvent('onfocusout');
                            }
                        };
                    })(self,target,target.options[target.selectedIndex].innerHTML));
                default: return;
2个回答

3
虽然我同意整个表单只有一个事件监听器比每个元素都有一个监听器更好,但您必须评估决策的成本和收益。一个监听器的好处是减少内存占用。缺点是你必须使用复杂的代码技巧来解决浏览器错误实现事件的问题,增加执行时间,滥用事件类型,反复注册和取消注册事件监听器,可能会导致某些用户困惑。
回到好处,如果您只将相同的函数附加为所有元素的侦听器,则内存占用量不会太大。而且内存是当前计算机不缺乏的东西。
即使这不能回答您的问题,我建议您停止尝试使其工作,而是在每个表单元素上附加监听器。
稍微回答一下您的观点:
  1. 你确定吗?我已经尝试了 微软文档中的基本示例,在下拉菜单中选择选项后,IE7和IE8都会触发 onchange 监听器。甚至在使用上/下箭头更改选择时也能触发。

  2. 虽然确实无法轻松地将标签上的事件与受影响的复选框连接起来,但为什么要这样做呢?无论如何,change 事件都将在复选框上触发,而您只应关心该事件。但是,如果您必须从标签上的事件获取到复选框,则可以手动执行此操作。如果事件的目标是标签,则知道该标签与输入有关。根据您使用标签的方式,可以选择嵌套在标签中的 input 元素,或获取 labelfor 属性中找到的 ID 对应的元素。

  3. 修复 复选框在更改时返回先前的值 bug 的一种方法是在复选框的 focus 事件上执行 this.blur()

  4. 当您说“处理程序”时,是指 onfocusin 事件处理程序还是 changeDelegator?在重新激活选项卡时看到 focusin 事件是很正常的。我不确定为什么会多次触发此事件,因此只是猜测:一个可能是当前输入接收到的焦点;第二个可能是文档本身接收到的焦点;我不知道为什么会有第三个调用。我不理解您所说的“如果检查状态实际上已更改,则...如果我直接单击复选框一次”。


  1. 是的,我确定:在IE8中触发了更改事件,但它不会冒泡。这是一个已知问题。
  2. 在这两种情况下都调用了更改处理程序,这不是问题。然而,当单击标签时,处理程序在选中状态已更改后被调用,而在直接单击复选框时,选中状态即将更改。因此,如果 target.checked === true,它刚刚被选中了,还是即将取消选中?无法判断。字符用完了。
- Elias Van Ootegem
  1. 添加模糊和聚焦会导致IE崩溃:请注意,由于“onchange”不冒泡,我正在使用“onfocusin”和“onfocusout”事件。如果处理程序模糊并聚焦,它将无限地调用自身。
  2. 通过处理程序,我指的是两个:严格来说,“changeDelegator”只是一个函数,但我在处理程序的上下文中调用它(changeDelegator.apply(this,[e]);),因此我认为它是实际处理程序的扩展/延续。我知道“focusin”在选项卡被_恢复_时触发,但它仅针对由委托函数处理的每个事件触发。 字符....
- Elias Van Ootegem
我的意思是最后一件事很简单:我点击一个复选框(处理程序被触发,但选中状态不改变!),如果我再次点击该复选框,则处理程序不会被调用,但选中状态仍然会改变。如果我不再点击第二次,而是切换选项卡并返回,那么我曾经点击过的复选框似乎没有调用“focusin”事件。真的很奇怪。在性能方面:有很大的差异,特别是在非V8上运行时。将其附加到整个页面上的每个元素将累加到333(只计算了所有元素)个字符... - Elias Van Ootegem
回复1:是的;我从你的问题中理解到,除非你失去焦点,否则选择元素不会触发更改事件,但再次阅读后,我发现你的意思是你想要触发自定义事件分派器。 - Sergiu Dumitriu
关于2和3:您可以使用“propertychange”事件来处理复选框。它会在“checked”属性更改后正确触发,并且您将从复选框中获取正确的状态。 - Sergiu Dumitriu
显示剩余7条评论

3

好的,我再试试,这次我想到了一个比较不错的方法(在工作中它管用了-我已经尝试复制我写的代码,但是在几杯啤酒后可能会有一些错误,但精神仍然是相同的)

window.attachEvent('onload',function ieLoad()
{
    var mainDiv = document.getElementById('main');//main div, from here events will be delegated
    var checks = mainDiv.getElementsByTagName('input');//node list of all inputs
    var checkStates = {};
    for (var i=0;i<checks.length;i++)
    {
        if (checks[i].type === 'checkbox')
        {//get their checked states on load, this object serves as a reference
            checkStates[checks[i].id] = checks[i].checked;
        }
    }
    mainDiv.attachEvent('onfocusin',(function(initState)
    {//initState holds a reference to the checkStates object
        return function(e)
        {
            e = e || window.event;
            var target = e.target || e.srcElement;
            //id of checkboxes used as key, so no checking for tagName or type required
            if (!initState.hasOwnProperty(target.id) || target.checked === initState[target.id])
            {//don't call method if checkstate is unchanged, either. I'll explain in a minute
                return e;
            }
            initState[target.id] = target.checked;//set new checked-state
            changeDelegator.apply(target,[e]);//delegate
        };
    })(checkStates));
    window.detachEvent('onload',ieLoad);//avoid mem-leak with onload handler!
});

我发现对于单选按钮和复选框,焦点事件(focusin)在某些情况下会触发两次。使用一个对象来保存所有复选框的实际选中状态比使用单个处理程序更节省资源,而且它允许我在元素的值改变后再委派事件。

changeDelegator函数仅在需要时被调用,但是我在这里发布的匿名函数仍然被调用太多了,但是这种方法仍然优于使用单个处理程序。

我留下了选择器,但我也使它们工作了(类似的处理方式,在我的代码完整版本中,闭包有两个对象,我可以通过标记一个id,在需要时触发模糊事件,然后将客户端重定向)。最后,尽管我学到了一些新技巧,但我从这个练习中学到的主要教训是,对那个可怕、粗糙的高小鬼(指IE浏览器)的深深憎恨...但如果其他人想在IE中委派更改事件,他们应该知道它(几乎)是可能的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接