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

IT技术 javascript internet-explorer dom-events event-delegation
2021-03-10 14:05:06

更新:( 回顾,小提琴和赏金)

这个问题没有得到太多关注,所以我打算花一些时间在上面。我知道我的回答和问题都过于冗长。这就是我继续设置这个 fiddle的原因,在我看来,这是我目前必须用来接近冒泡change事件的那种代码的体面表示我试图解决的几个问题:

  1. pseudo-change事件不会在选择元素上触发,除非它失去焦点。在某些情况下,应在选择新值时重定向客户端。我如何实现这一目标?
  2. 单击标签以及复选框本身时都会调用处理程序。这本身就是您所期望的,但是由于事件冒泡,(AFAIK)无法确定单击了哪个元素。IE 的事件对象没有realTarget属性。
  3. checked通过单击标签更改IE 中复选框-state 时,一切正常(尽管它需要一些讨厌的解决方法),但是当直接单击复选框时,会调用处理程序,但选中状态保持不变,直到我单击第二次。然后值会更改,但不会调用处理程序。
  4. 当我切换到不同的选项卡并再次返回时,处理程序被多次调用。如果选中状态实际改变了三次,如果我只直接单击复选框一次,则两次。

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


我有一个网页,上面有超过 250 个选择元素和 20~30 个复选框。我还必须跟踪用户的行为,并采取适当的行动。因此,我很自然地委托更改事件,而不是添加数百个听众,恕我直言。

当然,IE - 公司政策:必须支持 IE8 - 在我需要时不会触发 onchange 事件。所以我试图伪造一个 onchange 事件。到目前为止,除了一件真正让我感到烦恼的事情之外,我所拥有的工作还算不错。
我正在使用onfocusinonfocusout注册事件。在某些情况下,当用户从 a select 元素中选择一个新值时,脚本应该立即响应。但是,只要 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任何选择具有焦点 ATM事件。唯一的问题是,99.9% 的用户会点击选择,因此该focusin事件也会触发 onclick。

为了解决这个问题,我创建了闭包,并将当前的 select-in-focus 和它的原始值作为参数传递给它。但是有些用户确实使用他们的键盘,这些用户通常在不更改值的情况下跳到下一个选择框。每次绑定一个新的onclick听众......我相信有HAS比检查所有更简单的方法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个回答

虽然我同意在整个表单上只有一个事件侦听器会更好,而不是多个侦听器,每个元素一个,但您必须评估您的决定的成本和收益。一个监听器的好处是减少了内存占用。不利的一面是,您必须使用如此复杂的代码技巧来绕过某个浏览器对事件的错误实现、增加了执行时间、滥用事件类型、反复注册和取消注册事件侦听器,这可能会给某些用户造成混淆。

回到好处,如果您只是为所有元素附加相同的函数作为侦听器,则内存占用不会那么大。内存是当前计算机不缺的东西。

即使这不能回答您的问题,我还是建议您停止尝试进行这项工作,而是在每个表单元素上附加侦听器。

为了解决你的问题:

  1. 你确定吗?我已经尝试了 Microsoft 文档中的基本示例,onchange在单击下拉列表中的选项后,IE7 和 IE8 都会触发侦听器。它甚至会在使用向上/向下键更改选择时触发。

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

  3. 修复复选框在更改错误时返回先前值的一种方法是对复选框执行this.blur()onfocus事件。

  4. 当您说“处理程序”时,您指的是onfocusin事件处理程序,或changeDelegator? focusin重新激活选项卡时看到事件触发是正常的我不确定为什么该事件被多次触发,所以我只是猜测:一个可能是活动输入接收的焦点;第二个可能是文档本身接收的焦点;我不知道为什么会发生第三个电话。我不明白“如果选中状态确实发生了变化,[...] 如果我只直接单击了一次复选框”是什么意思。

3. 添加模糊和焦点会导致 IE 崩溃:请注意,由于onchange没有冒泡,我正在使用onfocusinonfocusout事件。如果处理程序模糊并聚焦,它只会继续无限地调用自己 4. 处理程序我的意思是两者:严格来说changeDelegator只是一个函数,但我在处理程序的上下文 ( changeDelegator.apply(this,[e]);) 中调用一个函数,所以我认为它是是实际处理程序的扩展/延续。我知道revivedfocusin选项卡时会被触发,但它会为委托函数处理的每个事件触发。字符....
2021-04-20 14:05:06
回复 1:是的;我从您的问题中了解到,除非您专注,否则不会在 select 元素上触发更改事件,但是再次阅读,我明白您的意思是您希望触发自定义事件调度程序。
2021-04-26 14:05:06
我最后一件事的意思很简单:我单击一个复选框(处理程序被触发,但选中状态不会改变!),如果我第二次单击该复选框,则不会调用该处理程序,但是选中状态变化都是一样的。如果我没有再次单击它,而是切换选项卡并再次返回,则focusin似乎不会为我单击一次的那个复选框调用事件真的很奇怪。在性能方面:有很大的不同,尤其是当不在V8 上运行时。将它附加到整个页面上的每个元素将加起来 333(只是计算所有)elems!chars...
2021-05-11 14:05:06
1. 是的,我确信:在 IE8 中触发了更改事件,但它不会冒泡。这是一个已知问题2。在这两种情况下都调用了更改处理程序,这不是问题。但是当点击标签时,处理程序会在选中状态发生变化后调用,而直接点击复选框时,选中状态即将发生变化。那么,如果target.checked === true,它刚刚被检查过,还是会被取消检查?没办法说。字符用完
2021-05-13 14:05:06
我担心的不是内存,而是事件循环的规模。直接绑定会使我的 ajaxonreadystatechange回调(附加新的处理程序)复杂化,更不用说 IE 中的内存泄漏与不存在元素的侦听器。也就是说,是的,我的代码对我的一些同事来说可能具有挑战性,所以我花时间把它记录得很好,而且 - 谢天谢地 - 我不是这里唯一喜欢 JS 的人 :)。好吧,这就是我坚持认为个人绑定不是这里的选择的原因
2021-05-16 14:05:06

嗯,我有另一个打击它,我想出了一个相当不错的方法(在工作中它的伎俩-我试图复制我写的代码,但喝了几杯啤酒后,它可能包含了一些错误,但在精神不变

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函数仅在需要时调用,但我在这里发布的匿名函数仍然比我想要的更多地调用Waaaay,但这种方法仍然优于单个处理程序。

我省略了选择,但我也让它们工作(类似的情况,在我的代码的完整版本中,闭包有 2 个对象,我做了它,所以我可以标记一个 id,在需要时触发模糊事件,以及客户端被重定向)。
在运行结束时,尽管我学到了一些新技巧,但我从这个练习中学到的主要东西是对那个叫做 IE 的东西的可怕、坚韧不拔的魔像更加深切的憎恨……但是如果其他人可能想在 IE 中委托更改事件,知道它(几乎)是可能的