事件是JavaScript和HTML之间交互的手段,指的是文档或者浏览器窗口发生的一些特定交互。我们可以使用处理程序来预订事件,以便事件发生时执行相应的代码,在软件工程中,这种模式称之为观察者模式。

一 事件流、事件冒泡和事件捕获

当浏览器发展到第四代的时候,IE4和Netscape Communicator4两个团队遇到了一个问题。即页面上有一组同心圆,当点击圆心时,到底是这一组同心圆的哪个触发了事件?两个团队对整个问题给出了相同的认知,当圆心被点击时,同时被点击的还有它的父容器,父容器的父容器,直至整个页面。

事件流描述的是从页面中接收事件的顺序。

但是两个团队对事件流的顺序持截然相反的态度。

IE团队认为事件是从子元素到页面的事件冒泡流;而Netscape则认为事件是从页面层层传递到具体元素的事件捕获流。

二 DOM事件流

DOM2级事件规定的事件流包括三个阶段:事件捕获阶段,处于目标阶段,事件冒泡阶段。

以下面的简单页面为例,单击div元素会按照下图的顺序触发事件。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Event Bubbling</title>
</head>
<body>
   <div id="myDiv">Click Me</div>
</body>
</html>

DOM2级规范中表明:在事件流中,事件的目标(div元素)在捕获阶段不会接收到事件。这意味着在捕获阶段从1->2->3就结束了,下个阶段是“处于目标”阶段,于是事件在div上发生,并在事件处理中被看成是冒泡阶段的一部分。然后,冒泡阶段发生,事件又传播回文档。

上述的规范表明事件在捕获阶段是不会被触发的。

多数的浏览器在实现的时候都遵循了上述规范,但是IE9、Safari、Chrome、Firefox和Opera9.5及更高的版本都会在捕获阶段触发事件对象上的事件。结果,就是有两个机会在目标对象上面操作对象。

三 事件处理程序

响应事件的函数就叫做事件处理程序。为事件指定处理程序的方式有好几种,包括HTML事件处理程序、DOM0级事件处理程序和DOM2级事件处理程序以及IE事件处理程序。

3.1 HTML事件处理程序

某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值可以使一些能够执行的JavaScript代码,例如下面这样:

<input type="button" value="Click Me" onclick="alert('clicked!')">
<input type="button" value="Click Me" onclick="showMessage(this.value)">

除了像上述一样直接写可执行的JavaScript,还可以调用外部script或者js文件的方法。

这样指定事件处理程序可以创建一个event的事件对象,同时this的指向也等于事件的目标元素。

在创建的showMessage函数中,可以通过with来扩展它的作用域,可以像访问局部变量一样访问document及该元素本身的成员,如果input存在与form表单中,作用域也可以包含访问表单元素的入口,例如:

function showMessage(value){
  with(document){
    with(this.form){
      with(this){
        //do something...
      }
    }
  }
}

但是,这种声明事件处理程序的方法存在几个问题:

一是触发事件时事件还没加载的时差问题,

二是HTML和JS高度耦合的问题。因此现在很少使用HTML事件处理程序,转而使用JavaScript指定事件处理程序。

3.2 DOM0级事件处理程序

通过JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性,如下:

var mybtn = document.getElementById("myBtn");
mybtn.onclick = function(){
  alert(this.id)
}

如果这些代码在HTML之后,那么在代码没有运行到事件处理程序之前,点击效果不存在。

this指向的是目标元素,并且事件在冒泡阶段被捕获。

要删除该事件处理程序使用如下方法:

mybtn.onclick = null

3.3 DOM2级事件处理程序

DOM2级事件定义了两个方法,用于添加和删除事件处理程序:addEventListener()和removeEventListener()。所以DOM节点都包含这两个方法,并且它们都接收三个参数,要处理的事件名,事件处理函数,一个布尔值。布尔值为true,表示在捕获阶段调用事件处理程序,否则在冒泡阶段调用。

var mybtn = document.getElementById("myBtn");

mybtn.addEventListener('click',function(){
  alert(this.id)
},false)

使用DOM2级事件处理程序可以添加多个。

var mybtn = document.getElementById("myBtn");

mybtn.addEventListener('click',function(){
  alert(this.id)
},false)

mybtn.addEventListener('click',function(){
  alert(this.value)
},false)

 移除事件处理程序只能使用removeEventListener(),并且传入的事件处理函数必须和传入的相同,所以,我们应该在添加事件时将事件处理函数先赋值给一个变量,这样才能保证移除时的函数相同。

var mybtn = document.getElementById("myBtn");
var handler = function(){
  alert(this.id)
}

mybtn.addEventListener('click',handler,false)

mybtn.removeEventListener('click',handler,false)

大多数时候,我们都将事件处理程序添加在冒泡阶段,这样可以最大限度的兼容各种浏览器。

3.4 IE事件处理程序

IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。接收参数为事件处理名称和事件处理函数,由于IE8及更早的版本只支持事件冒泡,所以这两个方法都是添加在事件冒泡阶段。

var mybtn = document.getElementById("myBtn");
var handler = function(){
  alert(this.id)
}

mybtn.attachEvent('onclick',handler)

mybtn.detachEvent('onclick',handler)

使用IE添加的事件处理程序的作用域和DOM0级的方法不一样,事件处理程序在全局作用域中运行,this指向window。

并且,通过attachEvent添加的多个事件的顺序和DOM2级不一样,它是按照相反的顺序触发,即后添加的事件先触发。

3.5 跨浏览器的事件处理程序

既然事件处理程序因浏览器而不同,我们可以选择一些JavaScript库来实现,同时也可以自己实现跨浏览器的事件处理程序。

var EventUtils = {
  addHandler:function(element,type,handler){
    if(element.addEventListener){
      element.addEventListener(type,handler,false);
    }else if (element.attachEvent){
      element.attachEvent("on"+type,handler);
    }else {
      element["on"+type] = handler;
    }
  },
  removeHandler:function(){
    if(element.removeEventListener){
      element.removeEventListener(type,handler,false);
    }else if (element.detachEvent){
      element.detachEvent("on"+type,handler);
    }else {
      element["on"+type] = null;
    }
  }
}

四 事件对象

触发事件时,会产生一个事件对象event,这个对象包含着所有与事件有关的信息。

4.1DOM中的事件对象

通过DOM0和DOM2指定事件处理程序时,都会传入event对象,就如下面的例子。

var mybtn = document.getElementById("myBtn");

mybtn.addEventListener('click',function(event){
  alert(event.type)
},false)

mybtn.onclick = function(event){
  alert(event.type)
}

通过HTML添加事件处理程序时,变量event中也保存着event对象。

<input type="button" value="Click Me" onclick="alert(event.type)">

event中包含的常用元素有:

bubbles:表明事件是否冒泡

cancelable:表明是否可以取消事件的默认行为

defaultPrevented:为true表示已经调用了preventDefault()

detail:与事件相关的细节

eventPhase:调用事件处理程序的阶段。

type:事件类型

target:事件的目标

currentTarget:其事件处理程序当前正在处理事件的那个元素

preventDefault()

stopPropagation()

在这些事件属性中,对象this始终等于currentTarget的值,他们等于这个事件挂载的dom节点。而target则只包含事件的实际目标,即不管事件是在哪里被捕获的,最初点击的那个目标就等于target。

document.body.onclick = function(event){
  alert(event.currentTarget === document.body); //true
  alert(this === document.body); //true
  alert(event.target === document.getElementById("myBtn")); //true
}

4.2IE中的事件对象

在使用DOM0级添加事件处理程序时,event对象作为window对象的一个属性存在,可以使用如下方法获得:

var mybtn = document.getElementById("myBtn");

mybtn.onclick = function(){
  var event = window.event; //IE中
  alert(event.type)
}

如果是用attachEvent添加的,那么event可以作为参数传入,同时也可以通过window的属性来获得。如果是HTML特性指定的事件处理程序,那么和DOM中一样,通过event变量访问event对象。

IE的event对象同样也包含很多属性和方法。其中最重要的又如下几个:

srcElement:事件的目标,和target一样

returnFalse:默认值为true,如果设置为false就可以取消事件的默认行为。

cancelBubble:默认值为false,将其设置为true就可以取消事件冒泡。

type:事件类型

4.3跨浏览器的事件对象

事件对象在不同浏览器也不相同,所以扩展之前的EventUtil可以实现跨浏览器的增强。

var EventUtils = {
  addHandler:function(element,type,handler){
    if(element.addEventListener){
      element.addEventListener(type,handler,false);
    }else if (element.attachEvent){
      element.attachEvent("on"+type,handler);
    }else {
      element["on"+type] = handler;
    }
  },
  removeHandler:function(){
    if(element.removeEventListener){
      element.removeEventListener(type,handler,false);
    }else if (element.detachEvent){
      element.detachEvent("on"+type,handler);
    }else {
      element["on"+type] = null;
    }
  },
  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.returnFalse = false;
    }
  },
  stopPropagation:function(event){
    if(event.stopPropagation){
      event.stopPropagation()
    }else{
      event.cancelBubble = true;
    }
  }
}

五 内存和性能

在页面添加事件处理程序会导致页面性能变慢,可以通过一下几种手段来优化结构,提高性能。

1.使用事件委托

事件委托利用了事件冒泡的机制,只指定一个事件处理程序,来管理某一类型的所有事件。在JS的一些库中已经实现了事件委托,我们可以在开发过程中方便的使用,当然也可以手写一个事件委托,可以在参考这里【面试】手写原生js实现事件代理,并兼容浏览器

2.及时移除事件处理程序

不再使用事件时,应该及时的移除事件处理程序,以释放内存,移除时应注意事件函数必须是添加时的函数。

以上就是所有有关JS事件的核心概念,如有问题,欢迎交流。


本文转载:CSDN博客