javascript 之 事件篇

浏览: 147 发布日期: 2017-09-08 分类: html5

js 是采用异步事件驱动的机制来响应用户操作的,也就是说当用户对某个html元素进行操作的时候,会产生一个事件,该事件会驱动某些函数来处理。
事件源:产生事件的地方(html元素,窗口,其他等等);
事件:鼠标事件,键盘操作、HTML事件等等;
事件对象:当某个事件发生时,可能会产生一个 事件对象,该事件对象会封装好该事件的信息,传递给事件处理程序;
事件处理程序:响应用户事件的代码;
事件流:页面中接受事件的顺序。

事件流

JS事件流最早要从IE和网景公司的浏览器大战说起,IE提出的冒泡型事件流和Netscape 提出捕获型事件流。

事件冒泡

事件冒泡是指事件是按照特定的事件目标到最不特定的事件目标的顺序触发, 即从DOM树的叶子到根。当用户点击了一个<div>元素,click事件将按照<div>—><body>—><html>—>document的顺序进行传播。若在<div>和<body>上都定义了click事件,

<body id="s1">
    <div id="s2">
        s2s2s2s2
    </div>
</body>    
<script>
     var s1=document.getElementById('s1'), s2=document.getElementById('s2');
     s1.addEventListener("click",function(e){
         console.log('s1 冒泡模式下');
     },false);
     s2.addEventListener("click",function(e){
          console.log('s2 冒泡模式下');   
     },false);
</script>

点击s2输出:
s2 冒泡模式下
s1 冒泡模式下

事件捕获

事件捕获是指事件的传播是从最不特定的事件目标到最特定的事件目标,即从DOM树的根到叶子。当用户点击了一个<div>元素,采用事件捕获,则click事件将按照document—><html>—><body>—><div>的顺序进行传播。

<body id="s1">
    <div id="s2">
        s2s2s2s2
    </div>
</body>   
<script>
     var s1=document.getElementById('s1'), s2=document.getElementById('s2');
     s1.addEventListener("click",function(e){
          console.log('s1 捕获模式下');
     },true);
     s2.addEventListener("click",function(e){
         console.log('s2 捕获模式下');
      },true);
</script>

点击s2:
s1 捕获模式下
s2 捕获模式下

DOM事件流

在W3C组织的统一之下,JS支持了冒泡流和捕获流,但是目前低版本的IE浏览器还是只能支持冒泡流(IE6,7,8);
js事件流原理图如下:

DOM2级事件流被分为三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段。
事件捕获阶段:实际目标(<div>)在捕获阶段不会接收事件。也就是在捕获阶段,事件从document到<html>再到<body>就停止了。
处于目标阶段:事件在<div>上发生并处理。但是事件处理会被看成是冒泡阶段的一部分。
冒泡阶段:事件又传播回文档。
也就是说一个js事件流是从window开始,最后回到window的一个过程。

但是,注意上述描述的是DOM2级事件流的原理,也就是说不是所有的JS的事件流都是根据上图所示的过程推进的。

DOM level0不支持捕获事件的。

DOM level ???

DOM0:将要添加的事件处理程序直接赋给该对象的事件处理程序属性。

<body >
    <div id="wrapper">
        <input id="btn1" type="button" value="按钮1">
    </div>

</body>
<script>
    var btn1=document.getElementById('btn1'),wrapper=document.getElementById('wrapper');
    btn1.onclick=function(){
        alert("DOM0点击事件 1")
    };
    btn1.onclick=function(){
        alert("DOM0点击事件 2")
    };
    wrapper.onclick=function(){
        alert('wrapper 点击事件');
    }
</script>

使用IE8,firefox,chrome浏览器测试:

点击按钮弹出:‘DOM0点击事件 2’ 和 ‘wrapper 点击事件‘;
点击div弹出:‘wrapper 点击事件’;

从运行结果可以得出以下结论:
1、在DOM Level 0中的只能一个元素只能绑定一个事件,多个事件,后面的会覆盖掉前面的事件。
2、DOM Level 0不会阻止事件冒泡的发生,但是不支持事件捕获。
2、所有浏览器均支持。

DOM2级事件定义了两个方法,addEventListener()方法和removeEventListener()方法来处理和删除事件处理程序,当然,这个是标准的。
它们可以接收三个参数,
第一个参数:要处理的事件名,是一个字符串,但是要记得不要加“on”作为前缀,
第二个参数:作为事件处理的函数,
第三个参数:一个布尔值,这个布尔值为true时,那就在事件捕获阶段调用事件处理程序,如果是false那就在冒泡阶段调用事件处理程序。

    <body id="s1">
        <div id="s2">
            s2s2s2s2
        </div>
     </body>    
     <script>
         var s1=document.getElementById('s1'), s2=document.getElementById('s2');
          s1.addEventListener("click",function(e){
              alert('s1 冒泡模式下');
          },false);
          s2.addEventListener("click",function(e){
              alert('s2 冒泡模式下');   
          },false);
          s1.addEventListener("click",function(e){
              alert('s1 捕获模式下');
           },true);
          s2.addEventListener("click",function(e){
              alert('s2 捕获模式下');
          },true);
   </script>

使用chrome浏览器测试,弹出内容:
s1 捕获模式下
s2 捕获模式下
s2 冒泡模式下
s1 冒泡模式下

由此可以看出:
1、DOM Level 2可以在一个元素上面注册多个事件。
2、DOM Level 2支持事件捕获也支持事件冒泡,但捕获事件要比冒泡事件先触发。

上面是用chrome测试的,现在我们用IE8测试一下,点击了下,没有任何弹层,IE8以下浏览器不支持addEventListener函数,

事件委托(或称事件代理)

事件委托是冒泡型事件流的典型应用啊,什么是事件委托呢?也就是利用冒泡的原理,把事件加到父级上,触发执行效果。
上一段代码,方法一:

<ul id="wrapper">
    <li url="www.baidu.com">111</li>
    <li url="www.jd.com">222</li>
    <li url="www.tmall.com">333</li>
    <li url="www.zhihu.com">444</li>
</ul>
<script>
    (function(){
        var wrapper = document.getElementById("wrapper");
        var lists = wrapper.getElementsByTagName('li');
        for(var i=0;i<lists.length;i++){
            lists[i].onclick = function(){
                alert(this.getAttribute('url'));
            }
        }
    })();
 </script>

很简单的代码,现在分析一下DOM是怎么执行的,首先找到ul,然后遍历li,点击li的时候,找到目标li的位置,执行点击事件,这样每次点击都要找一次 li 哦,那我们使用事件委托如何处理呢?
方法二:

(function(){
        var wrapper = document.getElementById("wrapper");
        wrapper.onclick=function(e){
            var e=e || window.event;
            var target= e.target || e.srcElement;
            if(target.nodeName.toLocaleLowerCase()==='li'){
                alert(target.getAttribute('url'));
            }
        }
 })();

用父级ul做事件处理,当li被点击时,由于冒泡原理,事件就会冒泡到ul上,因为ul上有点击事件,所以事件就会触发,当然,这里当点击ul的时候,也是会触发的,所以我们在上面加了一个判断,event 对象提供了一个属性target,可以返回事件的目标节点,也就是说,target就可以表示为当前的事件操作的DOM,但是不是真正操作DOM。

点击li会触发事件了,且每次只执行一次dom操作,如果li数量很多的话,将大大减少dom的操作,优化的性能可想而知!

总结一:使用事件委托可以提高性能。

在项目中我们的DOM有时候是动态生成的,我们现在使用方法一,

(function(){
        var wrapper = document.getElementById("wrapper");
        var lists = wrapper.getElementsByTagName('li');
        
        for(var i=0;i<lists.length;i++){
            lists[i].onclick = function(){
                alert('test');
            }
        }

        var appendHtml=document.createElement('li');
        appendHtml.innerText='这是动态插入的';
        wrapper.appendChild(appendHtml);
    })();

你会发现,新增的li 是没有事件的,说明添加子节点的时候,事件没有一起添加进去的,这要怎么办呢?
解决问题方法是使用事件代理机制,当事件被抛到更上层的父节点的时候,我们通过检查事件的目标对象(target)来判断并获取事件源Li。

总结二:使用事件委托新添加的元素还会有之前的事件。

事件对象event

在触发某个事件时,会产生一个事件对象event。事件对象的作用是用来记录事件发生是一些相关的信息,注意事件对象只有在事件发生时才会产生,我们无法手动创建,event对象只有在事件处理程序执行期间,才会存在,执行完毕即销毁。

所有浏览器都支持event对象,event对象会传入DOM0级,DOM2级,HTML指定,的事件处理程序中,但支持的方式不同,所以也会涉及跨浏览器的部分。

DOM中的事件对象:
IE中将Event视作window对象属性,NetScape直接看做Event对象;浏览器兼容方法:

var e = event || window.event;

HTML事件处理程序中的event:

<input id="btn" type="button" value="click" onclick="alert('html事件处理程序'+event.type)"/>

事件对象重要属性和方法

event对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过所有事件都会包括下表列出的成员:

type: 事件的类型,如onlick中的click;
IE属性-> srcElement,标准属性->target: 触发事件的元素
更多事件属性和方法:http://www.w3school.com.cn/js...

事件处理程序

HTML事件处理程序

<p onclick="alert('hello')" id='s1'>点击</p>

DOM0级事件处理程序

获取对这个元素DOM对象的引用,用DOM的getElementById()等这种方式获取到对这个元素对象的引用,然后就是每个事件都在这个对象上用相应的属性,例如click事件,那么这个对象就有一个onclick属性与之对应,那你在这个对象的属性上绑定事件处理程序。

<script>
 var s1=document.getElementById('s1')
s1.onclick=function(){
        console.log('第一个点击事件!');
    }
    s1.onclick=function(){
        console.log('第二个点击事件!')
    }
</script>

显示内容:第二个点击事件!
DOM0中每个事件元素目标对于每个事件类型只能最多注册一个事件处理程序,若注册多个后面的会覆盖前面的,前面这个注册程序就不会执行了。

删除这个事件处理程序:

s1.onclick=null; 

DOM2级事件处理程序

DOM2级事件定义了两个方法,addEventListener()方法和removeEventListener()方法来处理和删除事件处理程序。
它们可以接收三个参数,第一个参数:要处理的事件名,是一个字符串,但是要记得不要加“on”作为前缀,第二个参数:作为事件处理的函数,第三个参数:一个布尔值,这个布尔值为true时,那就在事件捕获阶段调用事件处理程序,如果是false那就在冒泡阶段调用事件处理程序。

<script>
    var s1=document.getElementById('s1')
    s1.addEventListener("click",function(e){
         console.log('第一个点击事件!');

    },true);
    s1.addEventListener("click",function(e){
         console.log('第二个点击事件!')
    },true);
</script>

主要优点是可以为同一个对象的同一个事件绑定多个事件处理程序。并且注册的多个事件是按顺序执行的;
通过addEventListener添加的事件处理程序必须通过removeEventListener删除,且参数一致。
那么我们在上面代码基础上添加移除事件:

s1.removeEventListener('click',function(){
    console.log('取消点击事件');
},false);

再次点击还是显示:
第一个点击事件!
第二个点击事件!
并未去掉点击事件显示‘取消点击事件’,这又是怎么回事???
是因为:通过addEventListener添加的匿名函数将无法删除
修改代码:

function firstClick(){
    console.log('第一个点击事件!');
}
function secClick(){
     console.log('第二个点击事件!')
}
s1.addEventListener('click',firstClick,false);
s1.addEventListener('click',secClick,false);
s1.removeEventListener('click',secClick,false);

输出:第一个点击事件!

但是,这两个方法不支持除IE8及以下的其他版本的所有浏览器,那IE事件处理程序又改如何写呢???

IE事件处理程序

IE8及以下的浏览器实现了类似的添加移除方法:attachEvent()和detachEvent()。

这两个方法都需要两个参数:事件处理程序名称和事件处理程序函数。注意是事件处理名称,所以需要加‘on’,由于IE8及以下版本的浏览器只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都是冒泡事件。

s1.attachEvent("onclick",function(){
   alert("第一个点击事件!");
});
s1.attachEvent("onclick",function(){
    alert("第二个点击事件!");
});

IE8及以下弹出的是:
第二个点击事件!
第一个点击事件!
detachEvent删除事件和removeEventListener删除事件一样,添加的匿名函数无法删除。

跨浏览器的事件处理程序

var eventUtil={
    addEvent:function(el,type,handler){
         if(el.addEventListener){
              el.addEventListener(type,handler,false);
         }else if(el.attachEvent){
              el.attachEvent('on'+type,handler)
         }else {
               element['on'+type]=handler;
          }
       },
    removeEvent:function(el,type,handler){
          if(el.removeEventListener){
               el.removeEventListener(type,handler,false)
          }else if(el.detachEvent){
               el.detach('on'+type,handler)
          }else {
               el['on'+type]=null;
          }
     }
  }

参考的文档:
http://www.jb51.net/article/7...
http://blog.csdn.net/jsdcheny...
http://www.cnblogs.com/todayh...

返回顶部