onchange事件的事件代理
实现对onchange事件的事件代理是最为复杂的,在FF与最新版的opera中,它是能冒泡到顶层对象window;对于其他标准浏览器,由于它的事件监听器拥有三个参数,我们将最后一个设为true,实施捕获就一了百了;但对于IE就麻烦,既不能冒泡又不能使用捕获,唯一可行就是使用事件模拟,换言之,使用其他事件代替onchange的效果。jQuery动用了四种事件来模拟它,通过对它的深入研究,遂放弃它的设计,搞出自己的方案出来。
关键点有两个:
- 监听元素(组)的状态变化
- 使用何种事件充当伪onchange事件。
先解决第一个问题。能使用onchange事件的元素大抵有如下这些(暂时不考虑HTML5新增的)
< form id="aaa" > < select name="sweets" multiple="multiple" id="bbb" > < option >Chocolate</ option > < option selected="selected">Candy</ option > < option >Taffy</ option > < option selected="selected">Caramel</ option > < option >Fudge</ option > < option >Cookie</ option > </ select >< br > < input type="file"/>< br /> < input type="radio" name="r" > < input type="radio" name="r" > < input type="radio" name="r" >< br > < input type="checkbox" name="ddd" > < input type="checkbox" name="ddd" >< br > < input value="文本域" id="eee" >< br > < textarea >文本区</ textarea > </ form > |
要监听它们的状态,首先要知道它是什么样子,然后到我们这个时点又是什么样子。这个样子,可以通过对元素的value,checked,selected等进行比较。但jQuery犯了个错误,有些元素是一组取值才有意义,如下拉框(这个jQuery是对的),还有checkbox与radio。看下面实验。
< form action=""> < fieldset >< legend >实验1</ legend > < input type="radio" name="r" onclick="alert(this.checked)"> < input type="radio" name="r" onclick="alert(this.checked)"> < input type="radio" name="r" onclick="alert(this.checked)">< br > < input type="checkbox" name="ddd" onclick="alert(this.checked)"> < input type="checkbox" name="ddd" onclick="alert(this.checked)">< br > </ fieldset > </ form > |
我们发现radio很独特,怎么点,它都是true,那岂不是不能区分它是否已发生变化吗?!我们换一种判定。
< form action=""> < fieldset >< legend >实验2</ legend > < input type="radio" name="gggg" onclick="getVal(this)"> < input type="radio" name="gggg" onclick="getVal(this)"> < input type="radio" name="gggg" onclick="getVal(this)">< br > < input type="checkbox" name="ddd2" onclick="getVal(this)"> < input type="checkbox" name="ddd2" onclick="getVal(this)">< br > </ fieldset > </ form > < script type="text/javascript"> var getVal = function(el){ var els = el.name ? el.ownerDocument.getElementsByName(el.name) : [el]; for(var i=0,ri = 0,re = [],el;el = els[i++];){ re[ri++] = el.checked } alert(re.join("-")) } </ script > |
对于类型为select-multiple的下拉框,我们也使用这种取值法,其他直接取value值就行了。下面是我的getVal函数:
var getVal = function ( el ) { var type = el.type, val = el.value, prop, array; if ( type === "select-multiple" ) { array = el.options, prop = "selected" ; } else if (type === "radio" || type === "checkbox" ) { array = el.name ? el.ownerDocument.getElementsByName(el.name) : [el]; } else if ( type === "select-one" ) { val = elem.selectedIndex; } if (array) { //如果不是select元素就把prop改为checked prop || (prop = "checked" ); // prop is "selected" or "checked" for ( var i=0,ri = 0,re = [],elem;elem = array[i++];){ re[ri++] = elem[prop]; } val = re.join( "-" ); } return val; } |
但在什么时候调用它呢。我们必须在使用伪onchange事件前取得一次值,把它保存起来,当使用onchange事件之时,再取一次,比较是否已发生变化,如果变化就执行回调函数,然后再保存新值。由于不同的元素onchange事件也有所不同,我们采取如下方式进行。
el.attachEvent( "onbeforeactivate" , function (){ var el = window.event.srcElement, type = el.type; if (/select/.test(type)){ //下拉框的数据修正在onbeforeactive事件中只会执行一次 if (el[ "_change_data" ] === undefined) el[ "_change_data" ] = getVal(el) } else { //其他表单元素则一直使用它进行数据修正 el[ "_change_data" ] = getVal(el) } }); |
数据修正是我自造的一个词,就是把表示表单元素的状态字段放到元素的一个自定义属性上,每次我们点击表单元素都把它取出来,与最新的值相比较。毫无疑问,想触发onchange事件,点击或输入等操作是必不可须。文本域,文本区的onchange事件是在失去焦点时触发的,而像下拉框,单选框,复选框则非常实时,一点击就触发,但下拉框的数据修正非常麻烦。像其他表单元素,肯定有个失去焦点的情况,但下拉框由于是一个元素集合,它是由select标签与option标签组成的(还可能有optgroup),我们通过e.scrElement得到事件源对象永远是select标签,在option之间点击,我们无法触发失去焦点的事件。注意,由于blur不会冒泡,在这里我们使用IE特有的focusout事件。因此对于文本域,文本区,上传域等表单元素,我们使用点击事件进行模拟,数据修正在onbeforeactive事件中进行。
el.attachEvent( "onfocusout" , function (){ testChange(focusoutChangeOne) }); |
testChange函数与jQuery非常不同。jQuery在此还使用了事件分派。我的实现没有这么绕,直接分用事件处理函数,循环执行所有回调函数。
var rselect = /select/, focusoutChangeOne = dom.oneObject([ "text" , "password" , "textarea" , "file" ]), clickChangeOne = dom.oneObject([ "radio" , "checkbox" , "select-multiple" , "select-one" ]), testChange = function (oneObject) { var e = dom.event.fix(window.event), el = e.target, type = el.type; e.live = true ; if (oneObject[type] && !el.readOnly){ var data = dom.store( el, "_change_data" ),val = getVal(el); if (data === undefined || val === data ) { return ; } if ( data != null || val ) { if (rselect.test(type)) dom.store(el, "_change_data" ,val) return dom.event.handle.call(el,e) } } } |
下面是我的事件系统,经典的DE大神架构……
dom.event = { add: function (){}, remove: function (){}, handle: function (){}, fix: function (){}, fire: function (){}, analog:{} } |
由于涉及到缓存系统,就无法演示了。不过在testChange 函数中,它还负责对下拉框的数据修正。说到onfocusout,IE中有经典的bug,就是单选按钮的onchange事件是由于失去焦点事件触发的,而不是用点击事件。
我与jQuery的事件系统也正是用onclick来模拟它。表单元素中像单选按钮,复选框,下拉框则在点击时就触发,因此它们用onclick模拟最合适。
el.attachEvent( "onclick" , function (){ testChange(clickChangeOne) }); |
嘛,难点已经厘清,有能力的人可以自己动手试试。
liveSetup:[ function (obj){ obj.attachEvent( "onbeforeactivate" , function (){ var el = window.event.srcElement, type = el.type; if (rselect.test(type)){ //数据修正 if (dom.store(el, "_change_data" ) === undefined) dom.store(el, "_change_data" ,getVal(el)) } else { dom.store(el, "_change_data" ,getVal(el)) } }); }, function (obj){ //对text textarea file password obj.attachEvent( "onfocusout" , function (){ testChange(focusoutChangeOne) //数据修正 }); }, function (obj){ //select checkbox radio obj.attachEvent( "onclick" , function (){ testChange(clickChangeOne) //事件调用与数据修正 }); }] |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述