初学JS小记(三)——事件

一、什么是事件

        事件就是浏览器或者用户执行的某个动作。比如:点击,鼠标移动,按下键盘等。HTML的界面和javascript代码之间就是通过事件进行交互的。当某个事件发生时,处理这个事件的程序我们称为事件处理程序(或事件监听器)。我们可以对某个类型的事件预先注册一个事件监听器,当这类型的事件发生时,就执行事件监听器中的代码,响应事件。eg:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 <div>
     <button></button>
 </div>
</body>
<script type="text/javascript">
    var btn = document.getElementsByTagName('button')[0];
    btn.onclick = function () {
        alert('click button');
    } 
</script>
</html>

        点击按钮时,就会弹出‘click button’的信息。


二、事件流

       所谓的事件流就是规定了页面中的元素按照怎么样的顺序来接收这个事件。比如,现在我们点击了页面上的一个按钮,按钮又属于body元素上的一部分,那我们究竟是算先点击了body还是button呢?事件流就是来解决这个问题的。最初提出这个问题的时候,不同的浏览器提供了不一样的实现标准,分别是:事件捕获和事件冒泡(IE)。

      事件捕获:就是最先接收事件的是页面上的根节点,然后在沿着dom树向下到最具体发生事件的那个元素

      事件冒泡:就是最先接收事件的是页面上最具体发生事件的元素,然后沿着dom树一层层往上传播到根节点。

      拿上面例子中的界面来讲,点击button时,节点接收事件的顺序如下:

初学JS小记(三)——事件初学JS小记(三)——事件

      后面为了规范事件流,在“DOM二级事件”中规定事件流为三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。如下:

初学JS小记(三)——事件

         现在的浏览器基本是按照这三个阶段来处理事件流的。事件从根节点传到具体节点,再顺着路径传回根节点。

         有人或许会问,为什么要规定三个阶段呢?只使用事件捕获或事件冒泡不是也可以行么?这一方面是因为在最初有两种事件流处理的方式,后来为了互相兼容,标准就将两中处理融合在一起;另一方面是为了方面编程人员自己可以选择是在事件捕获阶段还是事件冒泡阶段触发事件处理程序。下面会详细讲解。


三、为元素绑定事件或移除事件的方法

         事件处理程序的名字通常就是在事件名称前面加上‘on’。比如click事件的事件处理程序名称就是‘onclick’。 

        为界面的某个元素绑定一个事件的方法主要是以下三种:

1. HTML事件处理程序

        可以直接在html中通过一个与事件处理程序名称相同的属性,为元素添加一个事件处理程序。

<button onclick="alert('click button')"></button>
       可以直接在属性值中写出需要执行的代码(适合代码很少的情况),也可以将代码封装在一个函数中(函数在页面引用的javascript代码中),在属性值中调用函数,如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 <div>
     <button onclick="showMessege()"></button>
 </div>
</body>
<script type="text/javascript">
    function showMessege() {
        alert('click button');
    }
</script>
</html>
      函数可以在界面的<script></script>中,也可以在从外部引入的js文件中。

     这种方式添加的事件通过将与事件对应的属性值置为null,就可以移除事件。

var btn = document.getElementsByTagName('button')[0];
btn.onclick = null;

     这种方式有个很大的缺点就是HTML与javasript代码紧密耦合。需要修改事件时,不仅需要修改javascript中的代码,还需要修改HTML中的代码。所以这种方法一般不太经常使用。



2.DOM0级事件处理程序

       这种方式是只在javascript中为元素添加事件。与HTML中的代码无关。这种传统的方法是通过将一个函数赋值给一个事件处理程序属性。eg:

var btn = document.getElementsByTagName('button')[0];
btn.onclick =  function () {
    alert('click button');
}
      这里是将一个匿名函数赋值给了btn的onclick属性。也可以将一个已经定义好的函数赋值给事件处理程序属性。

function showMessege() {
    alert('click button');
}
var btn = document.getElementsByTagName('button')[0];
btn.onclick = showMessege;

      在这里切记使用已经定义的函数赋值时,函数后面不能加()。一旦加了(),是说明你调用了这个函数并且把函数的返回值赋值给你了btn.onclick属性。不会达到预期的效果。

       这种方式添加的事件和上面的方法一样,通过将与事件对应的属性值置为null,就可以移除事件。

var btn = document.getElementsByTagName('button')[0];
btn.onclick = null;
     

     PS: 这种方式添加的事件,是在事件冒泡阶段触发。什么意思呢,就是如果你同时为button和body添加了点击事件的事件处理程序。那么点击button的时候,事件流在事件冒泡阶段触发事件处理,按照上面的流程图就是先触发button的onclick,再触发body的onclick,即使在代码中是先为body添加onclick事件处理程序。eg:

r body = document.getElementsByTagName('body')[0];
 var btn = document.getElementsByTagName('button')[0];
 body.onclick = function () {
    alert('click body');
 };
 btn.onclick = function() {
       alert('click button');
   };
      点击按钮时,界面中会先出现‘click button’,在出现‘click body’,尽管代码中是先为body添加事件的。


3.dom2级事件处理程序

      在dom2级事件中添加了两个方法:addEventListener()和removeEvenListener()两个方法,用来添加和删除事件处理程序。所有的节点都有这两个方法。

       addEventListener(event,f,bool)

      removeEvenListener(event,f,bool)

      两个函数的参数一样。

      event:需要处理的事件名称

      f:       作为事件处理程序的函数

      bool: true:在事件捕获阶段调用事件处理程序,false:在事件冒泡阶段调用事件处理程序。

      以2中的代码为例,当点击button时,如果在事件捕获阶段调用事件处理程序,则,界面中会先出现‘click body’,在出现‘click  button’。如果是事件冒泡阶段调用,结果相反。下面的代码是在事件捕获阶段调用。一般情况都是在事件冒泡阶段调用。

var body = document.getElementsByTagName('body')[0];
 var btn = document.getElementsByTagName('button')[0];
function clickBody () {
    alert('click body');
 };
function clickButton() {
       alert('click button');
   };
body.addEventListener('click',clickBody,true);
btn.addEventListener('click',clickButton,true);

          可以将clickBody,clickButton换为匿名函数。

         使用这种方法可以为一个节点的一个事件添加多个事件处理程序函数

         对于在同一个节点上添加的多个事件处理程序,会按照添加它们的顺序执行。

btn.addEventListener('click',function () {
    alert('welcome');
},true);
btn.addEventListener('click',function () {
    alert('click');
},true);
          对于在btn上添加的两个事件处理程序,在点击时,会按照代码中添加的顺序执行。所以,会先出现‘welcome’,在出现‘click’。

         使用addEventListner天剑的事件处理程序,只能通过removeEventListener移除,而且移除时,需要保证所有的参数一致。所以,使用匿名函数的方式添加的事件处理程序,是没有办法移除的,即使removeEventListener中的匿名函数的写法与addEventListner中的匿名函数一致,可是在程序的角度,它们也不是同一个函数。上面代码添加的事件处理程序可以通过下面代码移除:

body.removeEventListener('click',clickBody,true);
btn.removeEventListener('click',clickButton,true);

PS:

         对于上面三种方式而言,事件处理程序是在添加该事件处理程序的元素中运行。也就是说在事件处理程序中的this是指向该元素的。拿3中的clickBody()函数讲,当在addEventListener中调用的时候,在这个函数内部的this,就是指向页面中的body元素的。

        

4.IE事件处理程序

       在IE6到IE8中,只支持事件冒泡,所以在这几款浏览器中不能使用addEventListener和removeEventListener添加和删除事件。在IE9及以后的浏览器中是支持的。在IE6到8中,使用attachEvent和detachEvent来添加删除事件。在IE9中也是支持attachEvent和detachEvent

btn.attachEvent('onclick',clickButton);
btn.detachEvent('onclick',clickButton);

        需要注意的有下面几点:

        (1)传入的事件名称是在click前面加上了‘on’,使用这两个方法添加时,传入的事件名称前面全部需要加上‘on’。

        (2)attachEvent和detachEvent中的事件处理程序是在全局作用域中运行。即,attachEvent和detachEvent的事件处理程序中的this指向window对象。

btn.attachEvent('onclick',function () {
    alert(window === this);
});
       点击btn时,界面显示‘true’。


5.兼容IE6到8中的事件处理程序

       通过下面一个类的封装,可以写出兼容IE6到8的事件添加和移除的程序

var EventUtil =   {
    addEvent :function (element,type,handler) {
         if(element.addEventListener) {
             element.addEventListener(type,handler,false);
         } else if (element.attachEvent) {
             element.attachEvent(type,handler);
         } else {
             element['on' + type] = handler;
         }
    },
    removeEvent: function (element,type,hander) {
          if(element.removeEventListener) {
              element.removeEventListener(type,hander,false);
          } else if (element.detach) {
              element.detach(type,hander);
          } else {
              element['on' + type] = null;
          }
    }
}
function clickButton() {
    alert('click button');
};
var btn = document.getElementsByTagName('button')[0];
EventUtil.addEvent(btn,'click',clickButton);
EventUtil.removeEvent(btn,'click',clickButton);


四、事件对性能的影响

        页面上事件处理程序的数量直接关系到页面的整体运行性能。一方面因为每个事件处理程序都是一个对象,会占用内存,内存中的对象越多,性能越差;另一方面,事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪事件。

       关于页面中的事件处理程序的优化可以从一下两方面着手:

        (1)使用事件委托。具体可以参见另一篇我的博客:

http://blog.csdn.net/u014182411/article/details/74452536

        (2)删除无用的事件处理程序

         对于在页面中不再使用的事件处理程序,即使删除。

        再删除页面中某个带事件处理程序的元素时,最好手动删除元素上的事件处理程序。因为,删除元素时,可能并没有删除与元素关联的事件处理程序,因为元素和事件处理程序之间存在引用,在某些浏览器在这种情况下会处理不恰当,导致元素和处理程序的引用都保存在内存中。