哈喽,前端的小伙伴们!在聊今天的 IE 兼容之前,还是先跟我一起问候下(日了)ie 的所有版本吧!
在现代浏览器中,对表单元素的输入监听一般是通过监听”input”事件来实现,但坑爹的是 ie8 及之前的版本是不支持这个事件的,基本会使用它的替代品——“propertychange”来模拟这个事件,但模拟总归是模拟,如下是我总结的它们之间的最大区别
propertychange
的触发条件并不仅仅是输入框的 value 被改变,任何属性的改变都会触发(比如:class,attribute 等)propertychange
事件不会冒泡,也就是说不能够像 oninput 那样进行事件托管propertychange
事件并不区分事件的调用来源,用户输入会触发,js 改变也会触发。而”input”事件往往都只需要关注用户的输入,这就容易造成事件的误触发
ok,下面来针对上面的每一条来一一给出解决办法,实现完美模拟!(全网独家!)
一、让 propertychange 只关注 value 变化
这里的 value 是个泛指,如果监听对象是 select,它的 value 就是 selectedIndex。
上代码:
1 | input.onpropertychange = function(e) { |
二、让 propertychange 事件冒泡
这个需要是否有点强 IE8 所难呢?的确,该事件本身是并不支持的,那我们只能想点歪门斜道了,通过监听”focusin”来变通实现。
实现思路如下:
这种需求一般是想要进行事件托管,通过监听表单元素的父级或者 document/window 对象来方便托管一切表单元素,这种实现方式稳定又高效,但这一切是基于该事件能够冒泡到顶层。虽然 propertychange 事件不支持冒泡,但”focusin”事件是支持的。不过它俩的职责不同啊,一个是监听属性变化,一个是监听焦点变化,如何联系?
大家想一下,如果要模拟 input 事件,一切的事件触发都是基于用户的输入,但输入之前必然得先让表单元素获取焦点,那是否可以这样,当输入框获取焦点的时候再绑定 propertychange 呢?
上代码:
1 | document.onfocusin = function(e) { |
三、让 propertychange 过滤 js 对值的改变
这条我觉得才是重头戏!网上也有相关实现,但解决方案无非两种:
- 干脆就不监听 propertychange,通过监听表单元素的”keydown”,”cut”,”paste”等一系列输入事件来模拟
- 在 js 设置 value 之前先主动告诉某变量我正在用 js 改变 value,propertychange 于是忽略。于是你在每次用 js 设置值之前都得先设置那个全局变量
好吧,我就不喷了,直接上我的解决办法,通过使用大家很少会用到的 Object.defineProperty。如果你已经了解了 defineProperty,我估计你已经知道我接下来要干啥了。
其实思路说出来很简单,就是如果在 js 改变表单元素值的时候,能自动通知我不就完事大吉了吗?而 defineProperty 就是干这事的!啊,不对,应该是一不小心干了这事。。
错误代码如下,错误代码如下,错误代码如下(重要的事情说三遍):
1 | // 是那个用来判断是否是js改变表单元素值的全局变量setValByJs |
为什么上面的代码是错的?因为会无限递归(好一个自言自语==)。由代码可以看出,在 set 方法的内部又调用了 this.value=xx,于是就会继续再调用 set,所以无限递归了。为毛非得”this.value=xx”呢,因为不这样的话,项目中的如下代码就会彻底失效了:
1 | input.value = '我是单身狗,汪汪汪' |
方法总比困难多,机智的我又想到另外一种设置 value 的办法:
1 | input.setAttribute('value', '我是单身狗,汪汪汪') |
ok,那再修改一下上面的代码,还是错误代码如下,错误代码如下,错误代码如下(重要的事情说三遍):
1 | // 是那个用来判断是否是js改变表单元素值的全局变量setValByJs |
那么问题来了,为毛上面的代码还错啊?!且听我仔细分析:
当 js 对表单元素设置值的时候,首先会触发 defineProperty 中对 value 定义的 set 方法,然后代码走啊走啊,当走到:
1 | this.setAttribute('value', val) |
这一行的时候,代码就会立刻跳到 input.onpropertychange 方法中去。。也就是说你还没来得及设置 setValByJs 呢,事件就被捕获了,故而对于托管方法来说,setValByJs 的值是啥永远是后知后觉的。为什么我会了解这么清楚?好吧,这都是我那时候遇到的坑,出于大家坑才是真的坑的心态,故放出来大家一起坑。所以上面的代码只需要把:
1 | setValByJs = true |
移到 set 方法的第一行即可。。
基本正确的代码如下:
1 | // 是那个用来判断是否是js改变表单元素值的全局变量setValByJs |
为什么又是基本正确呢?因为以上的例子基本全是事件的单一绑定,多绑定的坑还有很多。这里提一点最容易被坑的吧,就是这段代码:
1 | if (setValByJs) { |
如果该表单元素的 propertychange 事件绑定了多个监听方法,只有第一个方法里会获取到 setValByJs 的正确值,后面的获取到的永远都是 false.大家好好看下代码便知原因。这种情况也是得用点歪门斜道解决:
1 | if (setValByJs) { |
而且以上代码没有覆盖到所有表单元素,比如上文中提到的下拉选择框,它一般不直接监听 value
,不过核心思路都在这了,希望对你有用!希望世上再没有 IE!阿门。
好消息!
好消息!现在你只需要使用 fixJsForIE8 即可!引入它,然后:
1 | // 和现代浏览器的 input 事件保持一致 |