如果你真的想做一件事,你一定会找到方法;如果你不想做一件事,你一定会找到借口。

javaScript事件系统详解

一个有情怀的猴子🐒!

-----------------------------------------------------------------------------------------

本文目录:

  1、什么是javascript事件系统?(以及发展史简介)

  2、事件流

  3、事件处理程序

  4、事件对象

  5、事件类型

  6、内存和性能

  7、模拟事件

  8、总结

------------------------------------------------------------------------------------

1、什么是事件系统?(以及发展史简介)

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

  一个完整的事件系统,通常存在以下三个角色:

  • 事件对象,用于储存事件的状态。
  • 事件源对象,当前事件在操作的对象,如元素节点,文档对象,window对象,XMLHttpRequest对象等。
  • 事件监听器,当一个事件源生成一个事件对象时,它会调用相应的回调函数进行操作。在IE中,事件对象恒为全局属性window.event的分身。

  通俗点讲,事件源对象相当于”当事人“,事件监听器相当于”监护人“,事件对象相当于”事故详情“。一个事件可以理解为,当事人出了点事,至于什么事情(被打了,还是被抢了)都记录在事故详情里,监护人根据事故详情得做出点反应(回调函数)。

  说起来好像是挺简单,但其实不然。

  事件最早是在IE3和Netscape Navigator2中出现的,当时是作为分担服务器运算负载的一种手段。

  到IE4 和Navigator4发布时,这两种浏览器都提供了相似但不相同的API,而且这些API并存且经历了好几个版本更新。

  再后来,DOM2级规范开始尝试以一种符合逻辑的方式来标准化DOM事件。

  (IE9、Firefox、Opera、Safari和Chrome全都已经实现了”DOM2级事件“模块的核心部分。IE8是最后一个仍然使用其专有事件系统的主要浏览器。)

 

后话: 浏览器的事件系统相对比较复杂。尽管所有主要浏览器已经实现了”DOM2级事件“,但这个规范本身并没有涵盖所有的事件类型。浏览器对象模型(BOM)也支持一些事件,这些事件与文档对象模型(DOM)事件之间的关系并不十分清晰,因为BOM事件长期没有规范可以遵循(HMTL5后来给了详细说明)。随着DOM3级的出现,增强后的DOM事件API变的更加繁琐。使用事件有时相对简单,有时则非常复杂,难易程度会因为你的需求而不同。不过,有关事件的一些核心概念是一定要理解的。

有关JavaScript事件系统的发展史(javascript事件系统的发展史

----------------------------------------------------------------------

2、事件流

  当浏览器发展到第四代时(IE4及Netscape Communicator4),浏览器开发团队遇到了一个很有意思的问题。如下图所示,当我们点击目标事件的时候,不仅点击了自身,也点击了自身的容器,甚至点击了整个页面。如果这些元素都绑定了点击事件,那事件的执行顺序应该是怎样的?(暂时可以忽略图中的文字性描述)

  

  事件流描述的就是从页面中接受事件的顺序。但有意思的是,IE和Netscape团队提出了几乎完全相反的事件流概念。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕获流。

  

  2.1 事件冒泡流 与 事件捕获流

  事件冒泡流:事件开始由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。(由内及外)

  事件捕获流:由不太具体的节点更早接收到事件,而最具体的节点应该最后接收到事件。(由外及内)

  

注意:

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

  2)、IE9、Firefox、Chrome、Opera、和Safari都支持事件捕获。尽管DOM标准要求事件应该从document对象开始传播,但这些浏览器都是从window对象开始捕获事件的。

  3)、由于老版本浏览器不支持,很少有人使用事件捕获。建议使用事件冒泡。有特殊情况再使用捕获。

  

  2.2 DOM2级事件流

   “DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。如图所示:

   

  捕获阶段:实际目标(<div>元素)在捕获阶段不会接收事件,意思是事件从 [ document->html->body ] 后就停止了。【1、2、3】

  目标阶段:事件在目标元素上发生。但事件处理被看作是冒泡阶段的一部分。

  冒泡阶段:从目标元素开始处理事件,一直传播到文档。也就是 [ div->body->html->document  ]【4、5、6、7】

 注意:

1、“DOM2级事件”规范明确要求捕获阶段不会涉及实际目标的事件,但IE9、Chrome、Firefox、Safari和Opera9.5及更高版本都会在捕获阶段触发实际目标上的事件。结果,目标对象上的事件就会执行两次!

2、并非所有的事件都会有冒泡阶段。但所有的事件都会经过捕获阶段和处于目标阶段。eg:跳过冒泡阶段的事件:获得输入焦点的focus事件和失去输入焦点的blur事件

附一张自己画的图:(注意:处于目标阶段事件发生,但事件的处理属于冒泡阶段)

目标对象上的事件执行两次,实例代码:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<style>
    #outer{
        position: absolute;
        width: 400px;
        height: 400px;
        top:0;
        left: 0;
        bottom:0;
        right: 0;
        margin: auto;
        background-color: deeppink;
    }
    #middle{
        position: absolute;
        width: 300px;
        height:300px;
        top:50%;
        left: 50%;
        margin-left: -150px;
        margin-top: -150px;
        background-color: deepskyblue;
    }
    #inner{
        position: absolute;
        width: 100px;
        height:100px;
        top:50%;
        left:50%;
        margin-left: -50px;
        margin-top: -50px;;
        background-color: darkgreen;
        text-align: center;
        line-height: 100px;
        color:white;
    }
    #outer,#middle,#inner{
        border-radius:100%;
    }
</style>
<body>
<div id="outer">
    <div id="middle">
        <div id="inner">
            click me!
        </div>
    </div>
</div>
<script>
    var outerCircle= document.getElementById("outer");
    var middleCircle= document.getElementById("middle");
    var innerCircle= document.getElementById("inner");
    /* 事件捕获阶段 */
    outerCircle.addEventListener("click", function () {
        console.log("outerCircle的click事件在捕获阶段被触发");
    },true);
    middleCircle.addEventListener("click", function () {
        console.log("middleCircle的click事件在捕获阶段被触发");
    },true);
    /* 按照DOM2规范,应该不会触发该事件。但事实是触发了  */
    innerCircle.addEventListener("click", function () {
        console.log("innerCircle的click事件在捕获阶段被触发");
    },true);
    
    /* 处于目标阶段 ,事件的发生和处理,不过处理属于冒泡阶段 */
    /* 事件冒泡阶段 */
    innerCircle.addEventListener("click", function () {
        console.log("innerCircle的click事件在冒泡阶段被触发");
    },false);
    middleCircle.addEventListener("click", function () {
        console.log("middleCircle的click事件在冒泡阶段被触发");
    },false);
    outerCircle.addEventListener("click", function () {
        console.log("outerCircle的click事件在冒泡阶段被触发");
    },false);
</script>
</body>
</html>
View Code

 

3、事件处理程序

  事件就是用户或浏览器自身执行的某种动作。如 click、load 和 mouseover、mousedown 等

  响应某个事件的函数叫做事件处理程序(或事件侦听器)。

  有时候也把为事件指定处理程序的方式叫做事件处理程序,不过概念无所谓了,理解就行。按照这个说法,click事件的事件处理程序是onclick,load事件的事件处理程序就是onload。为事件指定处理程序的方式有有好几种。如下图所示:

注意: 由于HTML事件处理程序中HTML和JavaScript紧密耦合,所以已被大多程序员摒弃

      所谓跨浏览器事件处理程序,就是把HTML、DOM0、DOM2、IE的事件处理程序进行封装

  

  3.1 HTML事件处理程序 addEventListener(type, listener[, useCapture]);

  某个元素支持每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值应该是可以执行的JavaScript代码。

  示例代码(3种):

<!-- 方法一 -->
<input type="button" value="Click Me" onclick="alert('clicked!')" />

<!-- 方法二  如果单双引号不能叉开使用,必要时可以使用转义后的HTML语法字符 -->
<input type="button" value="Click Me" onclick="alert(&quot;clicked!&quot;)" />

<!-- 方法三 -->
<input type="button" value="Click Me" onclick="showMessage()" />
<script type="text/javascript">
    function showMessage(){
        alert('clicked!');
    }
</script>

其实事件每发生一次,就会创建一个封装着事件相关信息的函数,这个函数中有一个局部变量event,也就是事件对象(稍后介绍)。通过event变量,我们就可以直接访问事件对象,而不用自己定义,也不用从函数的参数列表中读取。同时,我们也可以通过这个事件对象获取目标元素。

  获取目标元素3种方式,示例代码如下:

<!-- 方法一 IE9、Firefox、chrome、Opera、safari支持(IE8及其以下不支持) -->
<input type="button" value="Click Me" onclick="console.log(event.target)" />

<!-- 方法二 主要是为了IE8以下兼容,同时其他高级浏览器也还支持 -->
<input type="button" value="Click Me" onclick="console.log(event.srcElement)"/>

<!-- 方法二  JavaScript中的this比较乱,如果不是很清楚,建议慎用-->
<input type="button" value="Click Me" onclick="console.log(this)" />

HTML事件处理程序的缺点:

  1、时差问题:用户可能在HTML元素一出现在页面上就触发事件,此时事件处理程序有可能尚不具备执行条件。解决办法,try-catch 。

  2、耦合度问题:HTML代码与JavaScript代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:HTML代码和JavaScript代码。

 

  3.2 DOM0级事件处理程序

  通过JavaScript指定事件处理程序,就是将一个函数赋值为一个事件处理程序属性(eg: 赋值给 onclick )。

  以这种方式添加的事件,会在事件流的冒泡阶段被处理。

  优点:所有浏览器支持,简单,跨浏览器支持

<input type="button" name="clicker" id="clicker" value="点击" />
<script type="text/javascript">
    var clicker = document.getElementById("clicker");
    clicker.onclick = function(){
        console.log("点击了!");
    }
</script>

  缺点:绑定事件不能累加,最后绑定的会覆盖之前的。(DOM2级事件处理程序解决了这个问题,稍后详解)

<input type="button" name="clicker" id="clicker" value="点击" />
<script type="text/javascript">
    var clicker = document.getElementById("clicker");
    clicker.onclick = function(){
        aler("第一次点击!");
    }
    clicker.onclick = function(){
        alert("第二次点击!");
    }
</script>

只会弹出第二次点击,而不会显示第一次的,如下图所示:

  

  也可以删除通过DOM0级方法指定的事件处理程序,就是将事件处理程序设置为null。

  设置之后,再点击就不会有任何动作发生。方法如下:

clicker.onclick = null;

注意:

  使用HTML事件处理程序指定的程序,可以被DOM0级事件处理程序覆盖,也可以以同样方式删除。

 

  3.3 DOM2级事件处理程序

  DOM2级事件定义了两个方法,用于处理和删除指定的事件处理程序。

  添加事件:addEventListener()

/*
 * 参数意义
 * target 目标元素
 * type 表示监听事件类型的字符串。
 * listener 事件的处理程序,
 * (listener 必须是一个实现了 EventListener 接口的对象或者函数.当所监听的事件类型触发时,会接收到一个事件通知对象(实现了 Event 接口的对象)
 * useCapture Boolean类型值,默认false,实现事件冒泡。若设置为true,实现事件捕获。
 * */
target.addEventListener(type, listener[, useCapture]);

  移除事件: removeEventListener().

/*
 * 参数意义
 * target 目标元素
 * type 一个字符串,表示需要移除的事件类型,如 "click"。
 * listener 需要移除的 EventListener 函数(先前使用 addEventListener 方法定义的)
 * useCapture 指定需要移除的 EventListener 函数是否为事件捕获。如果无此参数,默认值为 false。
 * */
target.removeEventListener(type, listener[, useCapture])

  DOM2级事件处理程序的主要好处是可以添加多个事件处理程序。然后按顺序触发

<input type="button" name="btn" id="btn" value="button" />
    
<script type="text/javascript">
    var btn = document.getElementById("btn");
    
    btn.addEventListener("click", function(){
        console.log('第一个注册事件执行了!');
    })
    btn.addEventListener("click", function(){
        console.log('第二个注册事件执行了!');
    })        
</script>

  通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除;

  移除时传入的参数和添加时使用的参数相同。

  这也意味着添加的匿名函数将无法移除。

<input type="button" name="btn" id="btn" value="button" />
    
<script type="text/javascript">
    var btn = document.getElementById("btn");
    
    btn.addEventListener("click", first, false)
    btn.addEventListener("click", function(){
        console.log('第二个注册事件执行了!');
    },false)    
    
    //移除事件的参数与addEventListener时的参数相同 ,
    btn.removeEventListener("click", first, false);
    //如果是匿名函数,将无法移除
    btn.removeEventListener("click", function(){
        console.log('第二个注册事件执行了!');
    },false)
    
    function first(){
        console.log('第一个注册事件执行了!');
    }
</script>

注意:

1、如果同一个监听事件分别为“事件捕获”和“事件冒泡”注册了一次,一共两次,这两次事件需要分别移除。两者不会互相干扰。 

2、为最大限度的兼容各种浏览器,建议将事件处理程序添加到事件流的冒泡阶段

3、除非特殊需要,否则不建议在事件捕获阶段注册事件处理程序 

  3.3 IE事件处理程序

  方法:注册事件处理程序 attachEvent() 、 移除事件处理程序 detachEvent()

  语法:attached = target.attachEvent(eventNameWithOncallback)

 

  区别addEventListener:

  1、attachEvent 是非标准的,addEventListener是标准的

  2、attachEvent只有两个参数,第一参数是事件类型(带“on”的,比如click,要写成 “onclick”)、第二个参数写法与addEventListener相同,没有三个参数,IE8及以下版本也不支持事件捕获

  3、事件处理程序中的作用域不同,也就是第二个参数中的this指向不同,DOM0、DOM2事件处理程序会在其元素的作用域内运行,this指向目标元素;而使用attachEvent()方法,事件处理程序会在全局作用域中运行,因此此处this指向window.

作用域示例代码 (IE8运行):

<input type="button" value="Click Me" id="clicker"/>
<script type="text/javascript">
    // 获取目标元素的引用
    var clicker = document.getElementById("clicker");
    // 注册事件处理程序
    clicker.attachEvent("onclick", showThis);
    function showThis(){
        alert(this);
    }
</script>

 

  3.4 跨浏览器事件处理程序

  事件的绑定、移除

<input type="button" value="Click Me" id="clicker"/>

<script type="text/javascript">
    var clicker = document.getElementById("clicker");
    // 兼容代码 
    var handing = {
        addEvent:function(target, type, listener, capture){
            if (target.addEventListener) {
                target.addEventListener(type, listener, capture);
            }else if(target.attachEvent){
                target.attachEvent("on" + type, listener);
            }else{
                target["on" + type] = listener;
            }
        },
        deleteEvent:function(target, type, listener, capture){
            if (target.removeEventListener) {
                target.removeEventListener(type, listener, capture);
            } else if(target.detachEvent){
                target.detachEvent("on" + type, listener);
            }else{
                target["on" + type] = null;
            }
        }
    }
    
    // 注册事件处理程序,须放在兼容代码的下面
    handing.addEvent(clicker, "click", showMessage, false);
    // 移除事件处理程序 
    handing.deleteEvent(clicker, "click", showMessage, false);
    function showMessage(){
        alert(this);
    }
</script>

  IE8以下this作用域的问题?

  http://www.51-n.com/t-4203-1-1.html

  解决方案:call()、apply()、bind() 详解

 

如果HTML事件处理程序、 DOM0级事件处理程序 和DOM2级事件处理程序同时存在??

答:HTML事件处理程序与DOM0级事件处理程序不能同时存在,会覆盖。

  且DOM0级事件处理程序不能累积添加。只执行最后一个添加的事件处理程序

  DOM2级事件程序不受HTML事件处理程序和DOM0级事件处理程序的影响。遵从先添加先执行的原则,可以累积添加事件

------------------------------------------------------------------------------------

 

 

尽量缩短篇幅:  javaScript事件系统详解(二)之[事件处理程序]与[事件对象]

参考链接:

javaScript事件(一)事件流

posted @ 2017-02-27 10:53  wanglehui  阅读(2164)  评论(0编辑  收藏  举报