JS事件处理机制及事件代理(事件委托)实例详解

 更新时间:2023年06月21日 09:27:09   作者:flying_huixia  
这篇文章主要介绍了JS事件处理机制及事件代理,结合实例形式详细分析了JS时间处理机制与事件代理功能、用法及相关使用技巧,需要的朋友可以参考下

一、先记个小知识点。cssText

cssText 本质:设置 HTML 元素的 style 属性值。

用法:document.getElementById("d1").style.cssText= "color:red; font-size:13px;";

cssText 返回值:在某些浏览器中(比如 Chrome),你给他赋什么值,它就返回什么值。在 IE 中则比较痛苦,它会格式化输出、会把属性大写、会改变属性顺序、会去掉最后一个分号,比如:

cssText的使用优势:样式一多,代码就很多;而且通过JS来覆写对象的样式是比较典型的一种销毁原样式并重建的过程,这种销毁和重建,都会增加浏览器的开销。语法为:obj.style.cssText=”样式”;这样就可以尽量避免页面reflow,提高页面性能。

但是,这样会有一个问题,会把原有的cssText清掉,比如原来的style中有’display:none;’,那么执行完上面的JS后,display就被删掉了。为了解决这个问题,可以采用cssText累加的方法:

Element.style.cssText += 'width:100px;height:100px;top:100px;left:100px;'

注意:上面cssText累加的方法在IE中是无效的。解决办法是,可以在前面添加一个分号来解决这个问题:

Element.style.cssText += ';width:100px;height:100px;top:100px;left:100px;'

补充:如果前面有样式表文件写着 div {text-decoration:underline; },这个会被覆盖吗?不会!因为它不是直接作用于 HTML元素的 style 属性。

二、JS的事件处理机制

1、事件流:指从页面中接收事件的顺序,有冒泡流和捕获流。

2、DOM2级事件规定事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。首先发生的是事件捕获,然后是实际的目标接收道事件,最后是冒泡阶段,可以在这个阶段对事件做出响应。

分析:实际的(text)元素在捕获阶段不会接收到事件,意味着在捕获阶段,事件从document到<body>再到<div>后就停止了。下一个阶段是“处于目标阶段”,于是事件在(text)上发生,并在事件处理中被看成是冒泡阶段的一部分。最后,冒泡阶段发生,事件又传播回文档。

3、事件处理程序

诸如click,load,mouseover都是事件的名字,而响应某个事件的函数就是事件处理程序(事件侦听器)。事件处理程序的名字以on开头,比如onclick.onmouseover等。

(1)HTML事件处理程序:某个元素支持的每种事件,都可以用一个相应事件处理程序同名的HTML特性来决定。

<input type="button" value="click" οnclick="alert('clicked')"/>
<input type="button" value="click" οnclick="alert(event.type)"/>

第二动态创建的函数中会有一个局部变量event,也就是事件对象。通过event变量,可以直接访问事件对象。

另外,这个动态创建的函数扩展作用域的方式如下:使用with

在这个函数内部,可以像访问局部变量一样访问document及该元素本身的成员。

function(){
   with(documnet){
       with(this){
          /元素属性值
     }
  }
}

(2)DOM0级事件处理程序

基于DOM0的事件,对于同一个dom节点而言,只能注册一个,后边注册的 同种事件 会覆盖之前注册的。利用这个原理我们可以解除事件,btn5.onclick=null;其中this就是绑定事件的那个元素;

这里添加的事件处理程序是在其依附的元素的作用域中运行。

DOM0级对每个事件只支持一个事件处理程序。

(3)DOM2级事件处理程序

DOM2支持同一dom元素注册多个同种事件,事件发生的顺序按照添加的顺序依次触发(IE是相反的)。DOM2事件通过addEventListener和removeEventListener管理。  DOM2级事件定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener(eventName,handlers,boolean)和removeEventListener(),两个方法都一样接收三个参数, 要处理的事件名,第二个是 事件处理程序函数,第三个值为 布尔值。
布尔值是true,表示在捕获阶段调用事件处理程序。false时表示在事件冒泡阶段调用事件处理程序,一般建议在冒泡阶段使用,特殊情况才在捕获阶段; 注意:通过addEventListener() 添加的事件处理程序只能用removeEventListener() 来移除,并且移除时传入的参数必须与添加时传入的参数一样;通过addEventListener()添加的匿名函数将无法移除。(js高程P351-P352)
使用DOM2级事件处理程序可以添加多个事件处理程序:

var btn2 = document.getElementById('btn2');
var handlers = function () {
   console.log(this.id);
 };
btn2.addEventListener('click',handlers,false);
btn2.addEventListener("click",function(){alert("hello")},false);
btn2.removeEventListener('click',handlers.false);

这里为按钮添加了两个事件处理程序,他们会按照添加他们的顺序触发。

(4)IE事件处理程序

IE用了attachEvent(),和detachEvent(),接收两个参数,事件名称和事件处理程序函数。由于IE8及以前只支持事件冒泡;通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。所以平时为了兼容更多的浏览器最好将事件添加到事件冒泡阶段。

 var btn3 = document.getElementById('btn3');
 var handlers2=function(){
  console.log(this===window);//true,注意attachEvent()添加的事件处理程序运行在全局作用域中;
 };
 btn3.attachEvent('onclick',handlers2);

分析:attachEvent()的第一个参数是“onclick”DOM则是“click”

重点:在使用attachEvent()方法的情况下,事件处理程序会在全局作用域中运行。因此this等于window。

attachEvent()也可以为同一元素添加两个不同的事件处理程序。只是执行事件时以相反的顺序被触发。

(5)跨浏览器事件处理程序

为了以跨浏览器的方式处理事件,有两个方法,addHandler(),它的职责是视情况分别使用DOM0和DOM2或者IE方法来添加或删除事件。这个方法属于一个名叫EventUtil的对象,可以处理浏览器差异。这个方法接收三个参数。要操作的元素、事件名称、和事件处理程序函数。对应的方法是removeHandler()函数,它的职责是移除事件处理程序。默认采用DOM0级方法。

4 事件对象

触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含了所有与事件有关的信息,比如导致事件的元素target,事件的类型,及其他特定的相关信息。例如鼠标操作导致的事件对象中会包含鼠标的位置,单双击等,而键盘操作导致的事件对象会包含按下的键等信息;

 事件被触发时,会默认给事件处理程序传入一个参数e , 表示事件对象;通过e,我们可以获得其中包含的与事件有关的信息;  只有在事件处理程序执行期间,event对象才会存在,一旦事件处理程序执行完毕,event对象就会被销毁;

(1)DOM中的事件对象

兼容DOM的浏览器会自动将一个事件对象event传递给事件处理程序。

在通过HTML特性指定事件处理函数时,变量event中保存着event对象。event对象包含与创建它的特定事件的有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。

currentTarget只读事件处理程序当前正在处理事件的那个元素
datail只读与事件相关的细节
eventPhase只读调用事件处理程序的阶段1 捕获阶段 2  处于目标 3 冒泡阶段
target只读事件的目标
type只读被触发的事件的类型

在事件处理程序内部,对象this始终等于currentTarget的值,target包含事件的实际目标。

ps:关于事件对象中的this,target,currentTarget,看个例子:(注:event.target不支持IE浏览器,应该用event.srcElement;还有 IE中通过attachment添加的事件是运行在全局作用域中的,this===window。

preventDefault() 阻止事件的默认行为,只有cancelabel属性的值设为true时,才可以使用preventDefalut.
event.stopPropagation()可以阻止事件的传播.,取消进一步的事件冒泡或者捕获

(2)IE中的事件对象

要访问IE的event对象有几种不同的方式,取决于指定事件处理程序的方法。

比如使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在,因此可以通过window.event来访问event对象。

var btn=document.getElementById("myBtn");
btn.οnclick=function(){
   var event=window.event;
   alert(event.type);
}

输出结果是click.
如果是使用attachEvent()来添加事件处理程序,那么会有一个对象作为参数传入事件处理程序函数中。

var btn=document.getElementById("myBtn");
btn.attachEvent("onclick",function(event){
   alert(event.type);
});

输出结果是click.

IE中event对象同样包含与创建它的事件相关的方法和属性。其中很多属性和方法都有对应的或者相关的DOM属性和方法。这些属性和方法会因为事类型的不同而不同。

srcElement只读事件的目标(与DOM中target属性相同)
type只读被触发的事件的类型
cancelBubble读/写默认值为false,设置为true可以取消事件冒泡(与DOM中stopPropagation()一样)
returnValue读/写默认为true,设置为false,就可以阻止默认行为。(与DOM中的preventDefault()一样)
将returnValue设置为false,就可以阻止默认行为。
cancelBubble属性值为true,可以取消事件冒泡。

(3)跨浏览器的事件对象

虽然DOM和IE中对象不同,但基于二者之间的相似性依旧可以拿出跨浏览器的方案来。

IE中的event中的全部信息和方法都是类似的只是实现方式不同,可以用前面提到过的EventUtil对象来求同存异。

var EventUtil(){
     addHandler:function(element,type,handler){
//省略代码
},
getEvent:function(event){
 return event?event:window.event;
   },
getTarget:function(event){
   return event.target||event.srcElement;
   },
preventDefault:function(event){
      if(event.preventDefault){
      event.preventDefault();
  }else{
      event.returnValue=false;
      }
   },
   removeHandler:function(element,type,handler){
    //省略代码
},
stopPropagation:function(event){
 if(event.stopPropagation){
      event.preventDefault();
  }else{
event.cancelBubble=true; }
}
};

以上代码为EventUtil 添加了4个方法;getEvent(),返回event对象的引用。其它方法类似。

5 事件委托

因为冒泡机制,比如既然点击子元素,也会触发父元素的点击事件,那我们完全可以将子元素的事件要做的事写到父元素的事件里,也就是将子元素的事件处理程序写到父元素的事件处理程序中,这就是事件委托;利用事件委托,只指定一个事件处理程序,就可以管理某一个类型的所有事件;

通俗来说:事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件,举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。

示例1:

<ul>
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>

实现点击li出现123.

传统方法:

window.onload = function(){
    var oUl = document.getElementById("ul1");
    var aLi = oUl.getElementsByTagName('li');
    for(var i=0;i<aLi.length;i++){
        aLi[i].onclick = function(){
            alert(123);
        }
    }
}

使用事件委托:

window.onload = function(){
    var oUl = document.getElementById("ul1");
   oUl.onclick = function(){
        alert(123);
    }
}

这里用父级ul做事件处理,当li被点击时,由于冒泡原理,事件就会冒泡到ul上,因为ul上有点击事件,所以事件就会触发,当然,这里当点击ul的时候,也是会触发的,那么问题就来了,如果我想让事件代理的效果跟直接给节点的事件效果一样怎么办,比如说只有点击li才会触发???

示例2:

Event对象提供了一个属性叫target,可以返回事件的目标节点,我们成为事件源,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom,当然,这个是有兼容性的,标准浏览器用ev.target,IE浏览器用event.srcElement。

window.onload = function(){
  var oUl = document.getElementById("ul1");
  oUl.onclick = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    if(target.nodeName.toLowerCase() == 'li'){
         alert(123);
         alert(target.innerHTML);
    }
  }
}

这样,只有点击li才会触发事件。

示例3

对比下列两段代码实现:

window.onload = function(){
            var oBtn = document.getElementById("btn");
            var oUl = document.getElementById("ul1");
            var aLi = oUl.getElementsByTagName('li');
            var num = 4;
            //鼠标移入变红,移出变白
            for(var i=0; i<aLi.length;i++){
                aLi[i].onmouseover = function(){
                    this.style.background = 'red';
                };
                aLi[i].onmouseout = function(){
                    this.style.background = '#fff';
                }
            }
            //添加新节点
            oBtn.onclick = function(){
                num++;
                var oLi = document.createElement('li');
                oLi.innerHTML = 111*num;
                oUl.appendChild(oLi);
            };
        }

注意:这里添加的新节点并不会有事件处理程序。

window.onload = function(){
            var oBtn = document.getElementById("btn");
            var oUl = document.getElementById("ul1");
            var aLi = oUl.getElementsByTagName('li');
            var num = 4;
            //事件委托,添加的子元素也有事件
            oUl.onmouseover = function(ev){
                var ev = ev || window.event;
                var target = ev.target || ev.srcElement;
                if(target.nodeName.toLowerCase() == 'li'){
                    target.style.background = "red";
                }
            };
            oUl.onmouseout = function(ev){
                var ev = ev || window.event;
                var target = ev.target || ev.srcElement;
                if(target.nodeName.toLowerCase() == 'li'){
                    target.style.background = "#fff";
                }
            };
            //添加新节点
            oBtn.onclick = function(){
                num++;
                var oLi = document.createElement('li');
                oLi.innerHTML = 111*num;
                oUl.appendChild(oLi);
            };
        }

用事件委托的方式,新添加的子元素是带有事件效果的,我们可以发现,当用事件委托的时候,根本就不需要去遍历元素的子节点,只需要给父级元素添加事件就好了,其他的都是在js里面的执行,这样可以大大的减少dom操作,这才是事件委托的精髓所在。

示例4: 点击某一个 Li 标签时,将 Li 的背景色显示在 P 标签内,并将 P 标签中的文字颜色设置成 Li 的背景色

传统实现:

 var list = document.querySelectorAll("li");
    for (var i = 0, len = list.length; i < len; i++) {
        list[i].onclick = function(e) {
            var t = e.target;
            var c = t.style.backgroundColor;
            var p = document.getElementsByClassName("color-picker")[0];
            p.innerHTML = c;
            p.style.color = c;
        }
    }

运用事件委托:

   var ulist=document.getElementsByClassName("palette")[0];
    ulist.οnclick=function(ev){
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if (target.nodeName.toLowerCase() === 'li') {
                var c = target.style.backgroundColor;
                var p = document.getElementsByClassName("color-picker")[0];
                p.innerHTML = c;
                 p.style.color = c;
        }
    }

注意:ul只有一个,要用索引,[0],如果不写,无法实现。
总结一下js委托相关的:

  • 因为把事件绑定到了父节点上,因此省了绑定事件。就算后面新增的子节点也有了相关事件,删除部分子节点不用去销毁对应节点上绑定的事件
  • 父节点是通过event.target来找对应的子节点的。(事件处理程序中的this值始终等于currentTarget的值,指向的是绑定到的那个元素)

相关文章

最新评论