zoukankan      html  css  js  c++  java
  • 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)//事件调用与数据修正
                            });
                        }]
    
  • 相关阅读:
    死磕 java线程系列之线程池深入解析——定时任务执行流程
    死磕 java线程系列之线程池深入解析——未来任务执行流程
    死磕 java线程系列之线程池深入解析——普通任务执行流程
    面试 LockSupport.park()会释放锁资源吗?
    死磕 java线程系列之线程池深入解析——生命周期
    死磕 java线程系列之线程的生命周期
    《动手学深度学习》系列笔记—— 1.2 Softmax回归与分类模型
    《动手学深度学习》系列笔记——1.1 线性回归
    【Python学习笔记】2. 高级变量类型
    【Python学习笔记】1. Python基础知识
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/1728970.html
Copyright © 2011-2022 走看看