Ruby's Louvre

每天学习一点点算法

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

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>
实验1

我们发现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>
实验2

对于类型为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)//事件调用与数据修正
    });
}]

如果您觉得此文有帮助,可以打赏点钱给我支付宝1669866773@qq.com ,或扫描二维码

posted on   司徒正美  阅读(7976)  评论(8编辑  收藏  举报

编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述
点击右上角即可分享
微信分享提示