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)//事件调用与数据修正
                        });
                    }]
原文地址:https://www.cnblogs.com/rubylouvre/p/1728970.html