事件流和事件处理程序学习笔记


Javascript与HTML之间的交互是通过 事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用 侦听器(或处理程序)来预定事件,以便事件发生时执行相应的代码。

事件流


事件流描述的是从页面中接收事件的顺序。主要有两种,一种是IE提出的事件冒泡流,一种是Neiscape Communicator提出的事件捕获流。

事件冒泡

就是时间开始时由具体的元素接受,然后逐级向上传播到较为不具体的结点。

所有现代浏览器都支持事件冒泡,但是有所区别。IE5.5及更早版本中的事件冒泡会跳过<html>元素(从<body>直接到document)。IE9,FireFox,Chrome和Safari则将事件一直冒泡到window对象。

事件捕获

思想主要是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。

事件捕获的用意在于在事件到达预定目标之前捕获他。

DOM事件流

"DOM2级事件"规定的事件流包括3个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。(所有的主要浏览器都已经实现了DOM2级事件从IE9开始)首先发生的是是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。

事件的执行顺序
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style type="text/css">
    #wrapDiv, #innerP, #textSpan{
      margin: 5px;padding: 5px;box-sizing: border-box;cursor: default;
    }
    #wrapDiv{
      width: 300px;height: 300px;border: indianred 3px solid;
    }
    #innerP{
      width: 200px;height: 200px;border: hotpink 3px solid;
    }
    #textSpan{
      display: block;width: 100px;height: 100px;border: orange 3px solid;
    }
  </style>
</head>
<body>
<div id="wrapDiv">wrapDiv
  <p id="innerP">innerP
    <span id="textSpan">textSpan</span>
  </p>
</div>
<script>
  var wrapDiv = document.getElementById("wrapDiv");
  var innerP = document.getElementById("innerP");
  var textSpan = document.getElementById("textSpan");

  // 测试直接绑定的事件到底发生在哪个阶段
  wrapDiv.onclick = function(){
    console.log("wrapDiv onclick 测试直接绑定的事件到底发生在哪个阶段")
  };

  // 捕获阶段绑定事件
  window.addEventListener("click", function(e){
    console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName);
  }, true);

  document.addEventListener("click", function(e){
    console.log("document 捕获", e.target.nodeName, e.currentTarget.nodeName);
  }, true);

  document.documentElement.addEventListener("click", function(e){
    console.log("documentElement 捕获", e.target.nodeName, e.currentTarget.nodeName);
  }, true);

  document.body.addEventListener("click", function(e){
    console.log("body 捕获", e.target.nodeName, e.currentTarget.nodeName);
  }, true);

  wrapDiv.addEventListener("click", function(e){
    console.log("wrapDiv 捕获", e.target.nodeName, e.currentTarget.nodeName);
  }, true);

  innerP.addEventListener("click", function(e){
    console.log("innerP 捕获", e.target.nodeName, e.currentTarget.nodeName);
  }, true);

  textSpan.addEventListener("click", function(){
    console.log("textSpan 冒泡 在捕获之前绑定的")
  }, false);

  textSpan.onclick = function(){
    console.log("textSpan onclick")
  };

  textSpan.addEventListener("click", function(e){
    console.log("textSpan 捕获", e.target.nodeName, e.currentTarget.nodeName);
  }, true);

  // 冒泡阶段绑定的事件
  window.addEventListener("click", function(e){
    console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName);
  }, false);

  document.addEventListener("click", function(e){
    console.log("document 冒泡", e.target.nodeName, e.currentTarget.nodeName);
  }, false);

  document.documentElement.addEventListener("click", function(e){
    console.log("documentElement 冒泡", e.target.nodeName, e.currentTarget.nodeName);
  }, false);

  document.body.addEventListener("click", function(e){
    console.log("body 冒泡", e.target.nodeName, e.currentTarget.nodeName);
  }, false);

  wrapDiv.addEventListener("click", function(e){
    console.log("wrapDiv 冒泡", e.target.nodeName, e.currentTarget.nodeName);
  }, false);

  innerP.addEventListener("click", function(e){
    console.log("innerP 冒泡", e.target.nodeName, e.currentTarget.nodeName);
  }, false);

  textSpan.addEventListener("click", function(e){
    console.log("textSpan 冒泡", e.target.nodeName, e.currentTarget.nodeName);
  }, false);
</script>
</body>
</html>

点击textSpan一次后

1540646371819

可得:先捕获,后冒泡,捕获从上到下,冒泡从下到上。

又可得onclick事件发生在冒泡阶段

再看对textSpan 的四个console.log();可以看到 有一个 console.log("textSpan 冒泡 在捕获之前绑定的")(代码第62到64行)是在下面的捕获之前执行的。

这里有另一个结论就是:如果对一个没有子元素的元素同时绑定冒泡和捕获,结果执行事件是 遵循Javascript的执行顺序。如果有子元素,则是先执行捕获,然后再执行冒泡。

换句话说:在元素上同时绑定捕获事件和冒泡事件,如果通过此元素的子级元素触发,则优先触发捕获事件,若不通过此元素的子级元素触发,则按照Javascript执行顺序触发。

W3C规范中定义了三个事件阶段,依次是捕获阶段,目标阶段,冒泡阶段。如果某个阶段不支持或事件对象的传播被终止,那么该阶段就会被跳过。

如果Event.bubbles属性被设置为false,那么冒泡阶段就会被跳过。如果Event.stopPropagation()在事件派发前被调用,那么所有的阶段都会被跳过。

在一个事件完成了所有阶段的传播路径后,它的Event.currentTarget会被设置为null并且Event.eventPhase会被设为0。Event的所有其他属性都不会改变(包括指向事件目标的Event.target属性)

事件处理程序


事件就是用户或浏览器自身执行的某种动作。而相应某个事件的函数就叫做事件处理程序(或事件侦听器)。为事件指定处理程序的方式有好几种。

HTML事件处理程序

  • 某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。

    • <input type="button" value="Click Me" onclick="alert('Clicked')">
      
  • 在HTML中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本。

    • 因为 事件处理程序中的代码在执行时,有权访问到全局作用域中的任何代码

这样指定事件处理程序有一些特点也有一些缺点:

  • 我们不能在JavaScript代码中使用未经转义的HTML语法字符,如&(和号)、""(双引号)、<(小于号)、>(大于号)等等,这个操作又是通过指定onclick特性并将一些javascript代码作为它的值来定义的,所以其中的特殊字符要转义。

  • 这样会创建一个封装着元素属性值的函数,这个函数中有一个局部变量event,也就是事件对象。

  • 缺点1:存在时差问题。因为用户可能会在HTML元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件,故报错。

    • 为此,很多HTML事件处理程序都会被封装到try-catch块中,以便错误不会浮出水面。

      <input type="button" value="click me" onclick="try{show();}catch(ex){}">
      
  • 缺点2:这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果。

    • 不同JavaScript引擎遵循的标识符解析规则略有差异,很可能会在访问非限定对象成员时出错。
  • 缺点3:HTML和JavaScript代码紧密耦合。如果要更换事件处理程序,就必须改动两个地方(HTML代码和JavaScript代码)

DOM0级事件处理程序

这种方法出现在第四代Web浏览器中,而且至今仍然为所有现代浏览器所支持。

原因一是简单,二是具有跨浏览器的优势。

var button=document.getElementById("button");
button.onclick=function(){
    alert("clicked");
}

即我们先在script中取得元素的引用,然后再将一个函数赋值给onclick事件处理程序。

之前介绍过,事件处理程序即为函数,而button.onclick这种形式即函数作为了对象的方法。那么对象的方法即事件处理程序是在元素(对象)的作用域中运行而非在全局作用域中运行的,因为方法是属于对象的。(注意:例4中事件处理程序是在全局作用域中运行的)。如果这个函数中存在this关键字,那么this就会指向这个对象。

我们还可以通过 button.onclick = null;的方式来删除DOM0级事件处理程序。

这个方式解决了HTML事件处理程序的三个缺点,但是它也有两个缺点

  • 我们不能给一个元素同时添加两个事件
  • 我们不能控制元素的事件流
DOM2级事件处理程序

DOM2级事件处理程序定义了两个方法:

  • addEventListener() —添加事件侦听器
  • removeEventListener() —删除事件侦听器

这两个方法都接收三个参数:要处理的事件名,作为事件处理程序的函数,表示事件流方式的布尔值(true则在捕获阶段调用事件处理程序,false则在冒泡阶段调用事件处理程序)

var button=document.getElementById("button");
button.addEventListener("click",function(){
    alert(this.id);
},false);

通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除;移除时传入的参数与添加处理程序时使用的参数相同。这也意味着通过addEventListener()添加的匿名函数将无法移除

大多数情况下,都是讲事件处理程序添加到冒泡阶段,这样可以最大限度的兼容各种浏览器。

IE事件处理程序

IE事件处理程序中有类似与DOM2级事件处理程序的两个方法:

  • attachEvent()添加

  • detachEvent()删除

由于IE8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。

他们接收2个相同的参数:事件处理程序名称(‘onclick’),事件处理程序函数。

注意:

  • IE事件处理程序中attachEvent()的事件处理程序的作用域和DOM0与DOM2不同,她的作用域是在全局作用域中。因此,不同于DOM0和DOM2中this指向元素,IE中的this指向window。
  • 同样,我们可以使用attachEvent()来给同一个元素添加多个事件处理程序。但是与DOM2不同,事件触发的顺序不是添加的顺序而是添加顺序的相反顺序。
  • 同样地,通过attachEvent()添加的事件处理程序必须通过detachEvent()方法移除,同样的,不能使用匿名函数。
  • 支持IE事件处理程序的浏览器不只有IE浏览器,还有Opera浏览器。
跨浏览器的事件处理程序

var EventUtil={
    addHandler:function(element,type,handler){
        if(element.addEventListener){
            element.addEventListener(type,handler,false);//注意:这里默认使用了false(冒泡)
        }else if(element.attachEvent){
            element.attachEvent("on"+type,handler);
        }else{
            element["on"+type]=handler;
        }
    },
    removeHandler:function(element,type,handler){
        if(element.removeEventListener){
            element.removeEventListener(type,handler,false);//注意:这里默认使用了false(冒泡)
        }else if(element.detachEvent){
            element.detachEvent("on"+type,handler);
        }else{
            element["on"+type]=null;
        }
    }
};

要构建两个函数一个是addHandler();一个是removeHandler()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值