JavaScript之事件处理详解

一.事件传播机制

  客户端JavaScript程序(就是浏览器啦)采用了异步事件驱动编程模型。当文档、浏览器、元素或与之相关的对象发生某些有趣的事情时,Web浏览器就会产生事件(event)。如果JavaScript应用程序关注特定类型的事件,那么它可以注册当这类事件发生时要调用的一个或多个函数。当然了,这种风格并非Web编程独有,所有使用图形用户界面的应用程序都采用了它。

  既然要详解事件处理,那我们先从几个基础概念说起吧:

    ①事件类型(event type):是一个用来说明发生什么类型事件的字符串。例如,“mousemove”表示用户移动鼠标,“keydown”表示键盘上某个键被按下。事件类型只是一个字符串,有时候又称之为事件名字(event name);

    ②事件目标(event target):是发生事件或与之相关的对象。Window、Document和Element对象是最常见的事件目标。当然,AJAX中的XMLHttpRequest对象也是一个事件目标;

    ③事件处理程序(event handler):是处理或响应事件的函数,它也叫事件监听程序(event listener)。应用程序通过指明事件类型和事件目标,在Web浏览器中注册它们的事件处理函数。

    ④事件对象(event object):是与特定事件相关且包含有关该事件详细信息的对象。事件对象作为参数传递给事件处理函数(但是在IE8以及其之前版本中,全局变量event才是事件对象)。事件对象都有用来指定事件类型(event type)的type属性和指定事件目标(event target)的target属性(但是在IE8以及其之前版本中,用的是srcElement而非target)。当然,不同类型的事件还会为其相关事件对象定义一些其他的独有属性。例如,鼠标事件的相关对象会包含鼠标指针的坐标,而键盘事件的相关对象会包含按下的键和辅助键的详细信息。

  以上说完了四个基本概念。那么问题来了——如果在一个web页面上用鼠标点击一个元素a的某一子元素b时,应该先执行子元素b注册的事件处理程序还是先执行元素a注册的事件处理程序呢(假设元素a和它的子元素b都有注册事件处理程序)?身为读者的你是否想过这个问题呢?

  这个问题就涉及到浏览器中的事件传播(event propagation)机制。相信大家都听说过事件冒泡(event bubble)和事件捕获(event capturing)吧!没错,它们就是浏览器中的事件传播机制。无图无真相,没有配图?那怎么阔以:

  看了图之后相信你已经大概理解了浏览器中的事件传播机制了:当一个事件发生时,它会先从浏览器顶级对象Window一路向下传递,一直传递到触发这个事件的那个元素,这也就是事件捕获过程。然而,一切并没有结束,事件又从这个元素一路向上传递到Window对象,这也就是事件冒泡过程(但是在IE8以及其之前版本中,事件模型并未定义捕获过程,只有冒泡过程)。

  所以,关于上面的问题,还得看元素a注册的事件处理程序是在捕获过程还是在冒泡过程了。那么到底什么是在捕获过程注册事件处理程序,在冒泡过程注册事件处理程序又是怎么做的呢?这就得好好说说几种注册事件处理程序的方式了:

1. 设置HTML标签属性为事件处理程序

  文档元素的事件处理程序属性,其名字由“on”后面跟着事件名组成,例如:onclick、onmouseover。当然了,这种形式只能为DOM元素注册事件处理程序。实例:

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>test</title>
    <style type="text/css">
        #div1{width: 300px; height: 300px; background: red; overflow:hidden;}
        #div2{margin:50px auto; width: 200px; height: 200px; background: green; overflow:hidden;}
        #div3{margin:50px auto; width: 100px; height: 100px; background: blue;}
    </style>
</head>
<body>
    <div id="div1" onClick="console.log(‘div1‘);">div1
        <div id="div2" oNClick="console.log(‘div2‘);">div2
            <div id="div3" onclick="console.log(‘div3‘);" onclick="console.log(‘div3333‘);">div3
            </div>
        </div>
     </div>
<script type="text/javascript">
</script>
</body>
</html>

结果(鼠标点击div3区域后):

从结果中可以看出:

  ①因为HTML里面不区分大小写,所以这里事件处理程序属性名大写、小写、大小混写均可,属性值就是相应事件处理程序的JavaScript代码;

  ②若给同一元素写多个onclick事件处理属性,浏览器只执行第一个onclick里面的代码,后面的会被忽略;

  ③这种形式是在事件冒泡过程中注册事件处理程序的;

2.设置JavaScript对象属性为事件处理程序

  可以通过设置某一事件目标的事件处理程序属性来为其注册相应的事件处理程序。事件处理程序属性名字由“on”后面跟着事件名组成,例如:onclick、onmouseover。实例:

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>test</title>
    <style type="text/css">
        #div1{width: 300px; height: 300px; background: red; overflow:hidden;}
        #div2{margin:50px auto; width: 200px; height: 200px; background: green; overflow:hidden;}
        #div3{margin:50px auto; width: 100px; height: 100px; background: blue;}
    </style>
</head>
<body>
    <div id="div1">div1
        <div id="div2">div2
            <div id="div3">div3
            </div>
        </div>
    </div>
<script type="text/javascript">
    var div1 = document.getElementById(‘div1‘);
    var div2 = document.getElementById(‘div2‘);
    var div3 = document.getElementById(‘div3‘);
  div1.onclick = function(){
    console.log(‘div1‘);
  };
  div2.onclick = function(){
    console.log(‘div2‘);
  };
  div3.onclick = function(){
    console.log(‘div3‘);
  };
  div1.onclick = function(){
    console.log(‘div11111‘);
  };

  div1.onClick = function(){
    console.log(‘DIV11111‘);
  };

</script>
</body>
</html>

结果(鼠标点击div3区域后):

从结果中可以看出:

  ①因为JavaScript是严格区分大小写的,所以,这种形式下属性名只能按规定小写;

  ②若给同一元素对象写多个onclick事件处理属性,后面写的会覆盖前面的(ps:这就是在修改一个对象属性的值,属性的值是唯一确定的);

  ③这种形式也是在事件冒泡过程中注册事件处理程序的;

3.addEventListener()

  前两种方式出现在Web初期,众多浏览器都有实现。而addEventListener()方法是标准事件模型中定义的。任何能成为事件目标的对象——这些对象包括Window对象、Document对象和所有文档元素等——都定义了一个名叫addEventListener()的方法,使用这个方法可以为事件目标注册事件处理程序。addEventListener()接受三个参数:第一个参数是要注册处理程序的事件类型,其值是字符串,但并不包括前缀“on”;第二个参数是指当指定类型的事件发生时应该调用的函数;第三个参数是布尔值,其可以忽略(某些旧的浏览器上不能忽略这个参数),默认值为false。这种情况是在事件冒泡过程中注册事件处理程序。当其为true时,就是在事件捕获过程中注册事件处理程序。实例:

<!DOCTYPE HTML>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title>test</title>
  <style type="text/css">
    #div1{width: 300px; height: 300px; background: red; overflow:hidden;}
    #div2{margin:50px auto; width: 200px; height: 200px; background: green; overflow:hidden;}
    #div3{margin:50px auto; width: 100px; height: 100px; background: blue;}
  </style>
</head>
<body>
  <div id="div1">div1
    <div id="div2">div2
      <div id="div3">div3
      </div>
    </div>
  </div>
<script type="text/javascript">
  var div1 = document.getElementById(‘div1‘);
  var div2 = document.getElementById(‘div2‘);
  var div3 = document.getElementById(‘div3‘);
  div1.addEventListener(‘click‘, function(){ console.log(‘div1-bubble‘); }, false);
  div2.addEventListener(‘click‘, function(){ console.log(‘div2-bubble‘); }, false);
  div3.addEventListener(‘click‘, function(){ console.log(‘div3-bubble‘); }, false);
  div3.addEventListener(‘click‘, function(){ console.log(‘div3-bubble222‘); }, false);
  div1.addEventListener(‘click‘, function(){ console.log(‘div1-capturing‘); }, true);
  div2.addEventListener(‘click‘, function(){ console.log(‘div2-capturing‘); }, true);
  div3.addEventListener(‘click‘, function(){ console.log(‘div3-capturing‘); }, true);
</script>
</body>
</html>

结果(鼠标点击div3区域后):

从结果中可以看出:

  ①addEventListener()第三个参数的作用正如上面所说;

  ②通过addEventListener()方法给同一对象注册多个同类型的事件,并不会发生忽略或覆盖,而是会按顺序依次执行;

相对addEventListener()的是removeEventListener()方法,它同样有三个参数,前两个参数自然跟addEventListener()的意义一样,而第三个参数也只需跟相应的addEventListener()的第三个参数保持一致即可,同样可以省略,默认值为false。它表示从对象中删除某个事件处理函数。实例:

div1.addEventListener(‘click‘, div1BubbleFun, false);
div1.removeEventListener(‘click‘, div1BubbleFun, false);
function div1BubbleFun(){
    console.log(‘div1-bubble‘);
}

4.attachEvent()

  但是,IE8以及其之前版本的浏览器并不支持addEventListener()和removeEventListener()。相应的,IE定义了类似的方法attachEvent()和detachEvent()。因为IE8以及其之前版本浏览器也不支持事件捕获,所以attachEvent()并不能注册捕获过程中的事件处理函数,因此attachEvent()和detachEvent()要求只有两个参数:事件类型和事件处理函数。而且,它们的第一个参数使用了带“on”前缀的事件处理程序属性名。实例:

var div1 = document.getElementById(‘div1‘);
div1.attachEvent(‘onclick‘, div1BubbleFun);
function div1BubbleFun(){
  console.log(‘div1-bubble‘);
}

  相应的,从对象上删除事件处理程序函数使用detachEvent()。例如:

div1.detachEvent(‘onclick‘, div1BubbleFun);

  到此为止,我们已经说了浏览器中事件传播机制以及各种注册事件处理程序的方法。下面我们就再说说事件处理程序调用时的一些问题吧!

二.事件处理程序的调用

1.事件处理程序的参数:正如前面所说,通常事件对象作为参数传递给事件处理函数,但IE8以及其之前版本的浏览器中全局变量event才是事件对象。所以,我们在写相关代码时应该注意兼容性问题。实例(给页面上id为div1的元素添加点击事件,当点击该元素时在控制台输出事件类型和被点击元素本身):

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>test</title>
    <style type="text/css">
        #div1{width: 300px; height: 300px; background: red; overflow: hidden;}
    </style>
</head>
<body>
    <div id="div1">div1</div>
    <script type="text/javascript">
        var div1 = document.getElementById(‘div1‘);
        if(div1.addEventListener){
            div1.addEventListener(‘click‘, div1Fun, false);
        }else if(div1.attachEvent){
            div1.attachEvent(‘onclick‘, div1Fun);
        }
        function div1Fun(event){
            event = event || window.event;
            var target = event.target || event.srcElement;
            console.log(event.type);
            console.log(target);
        }
    </script>
</body>
</html>

2.事件处理程序的运行环境:关于事件处理程序的运行环境,也就是在事件处理程序中调用上下文(this值)的指向问题,可以看下面四个实例。

  实例一:

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>test</title>
    <style type="text/css">
        #div1{width: 300px; height: 300px; background: red; overflow: hidden;}
    </style>
</head>
<body>
    <div id="div1" onclick="console.log(‘html:‘); console.log(this);">div1</div>
    <script type="text/javascript">
    </script>
</body>
</html>

  结果一:

  从结果可以看出:

    ①第一种方法事件处理程序中this指向这个元素本身;

  实例二:

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>test</title>
    <style type="text/css">
        #div1{width: 300px; height: 300px; background: red; overflow: hidden;}
    </style>
</head>
<body>
    <div id="div1" onclick="console.log(‘html:‘); console.log(this);">div1</div>
    <script type="text/javascript">
        var div1 = document.getElementById(‘div1‘);
        div1.onclick = function(){
            console.log(‘div1.onclick:‘);
            console.log(this);
        };
    </script>
</body>
</html>

  结果二:

  从结果可以看出:

    ①第二种方法事件处理程序中this也指向这个元素本身;

    ②存在第二种方法时,它会覆盖第一种方法注册的事件处理程序;

  实例三:

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>test</title>
    <style type="text/css">
        #div1{width: 300px; height: 300px; background: red; overflow: hidden;}
    </style>
</head>
<body>
    <div id="div1" onclick="console.log(‘html:‘); console.log(this);">div1</div>
    <script type="text/javascript">
        var div1 = document.getElementById(‘div1‘);
        div1.onclick = function(){
            console.log(‘div1.onclick:‘);
            console.log(this);
        };
        div1.addEventListener(‘click‘, function(){
            console.log(‘div1.addEventListener:‘);
            console.log(this);
        }, false);
    </script>
</body>
</html>

  结果三:

  从结果可以看出:

    ①第三种方法事件处理程序中this也指向这个元素本身;

    ②第三种方法并不会覆盖第一种或第二种方法注册的事件处理程序;

  实例四:

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>test</title>
    <style type="text/css">
        #div1{width: 300px; height: 300px; background: red; overflow: hidden;}
    </style>
</head>
<body>
    <div id="div1" onclick="console.log(‘html:‘); console.log(this);">div1</div>
    <script type="text/javascript">
        var div1 = document.getElementById(‘div1‘);
        div1.onclick = function(){
            console.log(‘div1.onclick:‘);
            console.log(this);
        };
        div1.attachEvent(‘onclick‘, function(){
            console.log(‘div1.attachEvent:‘);
            console.log(this === window);
        });

    </script>
</body>
</html>

  结果四:

  从结果可以看出:

    ①第四种方法事件处理程序中this指向全局对象Window;

    ②第四种方法也不会覆盖第一种或第二种方法注册的事件处理程序;

3.事件处理程序的调用顺序:多个事件处理程序调用规则如下:

  ①通过HTML属性注册的处理程序和通过设置对象属性的处理程序一直优先调用;

  ②使用addEventListener()注册的处理程序按照它们的注册顺序依次调用;

  ③使用attachEvent()注册的处理程序可能按照任何顺序调用,所以代码不应该依赖于调用顺序;

4.事件取消:

  ①取消事件的浏览器默认操作(比如点击超链接元素会自动发生页面跳转的默认操作):如果使用前两种方法注册事件处理程序,可以在处理程序中添加返回值false来取消事件的浏览器默认操作。在支持addEventListener()的浏览器中,也可以通过调用事件对象的preventDefault()方法取消事件的默认操作。至于IE8及其之前的浏览器可以通过设置事件对象的returnValue属性为false来取消事件的默认操作。参考代码:

function cancelHandler(event){
    var event = event || window.event;
    if(event.preventDefault){
        event.preventDefault();
    }
    if(event.returnValue){
        event.returnValue = false;
    }
    return false;
}

  ②取消事件传播:在支持addEventListener()的浏览器中,可以调用事件对象的一个stopPropagation()方法阻止事件的继续传播,它能工作在事件传播期间的任何阶段(捕获期阶段、事件目标本身、冒泡阶段);但是在IE8以及其之前版本的浏览器中并不支持stopPropagation()方法,而且这些浏览器也不支持事件传播的捕获阶段,相应的,IE事件对象有一个cancelBubble属性,设置这个属性为true能阻止事件进一步传播(即阻止其冒泡)。参考代码(阻止发生在div3区域的点击事件冒泡到div2和div1):

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>test</title>
    <style type="text/css">
        #div1{width: 300px; height: 300px; background: red; overflow:hidden;}
        #div2{margin:50px auto; width: 200px; height: 200px; background: green; overflow:hidden;}
        #div3{margin:50px auto; width: 100px; height: 100px; background: blue;}
    </style>
</head>
<body>
    <div id="div1">div1
        <div id="div2">div2
            <div id="div3">div3
            </div>
        </div>
    </div>
    <script type="text/javascript">
        var div1 = document.getElementById(‘div1‘);
        var div2 = document.getElementById(‘div2‘);
        var div3 = document.getElementById(‘div3‘);
       div1.onclick = function(){
            console.log(‘div1‘);
       };
       div2.onclick = function(){
            console.log(‘div2‘);
       };
       div3.onclick = function(event){
            stopEventPropagation(event);
            console.log(‘div3‘);
       };

        function stopEventPropagation(event){
            var event = event || window.event;
            if(event.stopPropagation){
                event.stopPropagation();
            }else{
                event.cancelBubble = true;
            }
        }
    </script>
</body>
</html>

  当然,关于事件冒泡还是有可利用之处的,这也就是我们常说的事件代理或者事件委托,我之前曾写过一篇文章介绍相关内容,详情请看:http://www.cnblogs.com/craftsman-gao/p/4811845.html 。

下面是在下衷心送给各位看官的五个欢迎(O(∩_∩)O~):欢迎转载、欢迎收藏、欢迎评论、欢迎点赞、欢迎推荐!

时间: 2024-10-13 17:36:06

JavaScript之事件处理详解的相关文章

代码示例:一些简单技巧优化JavaScript编译器工作详解,让你写出高性能运行的更快JavaScript代码

告诉你一些简单的技巧来优化JavaScript编译器工作,从而让你的JavaScript代码运行的更快.尤其是在你游戏中发现帧率下降或是当垃圾回收器有大量的工作要完成的时候. 单一同态: 当你定义了一个两个参数的函数,编译器会接受你的定义,如果函数参数的类型.个数或者返回值的类型改变编译器的工作会变得艰难.通常情况下,单一同态的数据结构和个数相同的参数会让你的程序会更好的工作. function example(a, b) { // 期望a,b都为数值类型 console.log(++a * +

Javascript 严格模式详解

Javascript 严格模式详解 作者: 阮一峰 日期: 2013年1月14日 一.概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict mode).顾名思义,这种模式使得Javascript在更严格的条件下运行. 设立"严格模式"的目的,主要有以下几个: - 消除Javascript语法的一些不合理.不严谨之处,减少一些怪异行为; - 消除代码运行的一些不安全之处,保证代码运行的安全: - 提高编译器效率,增加运行速度

iOS开发——实用技术OC篇&amp;事件处理详解

事件处理详解 一:事件处理 事件处理常见属性: 事件类型 @property(nonatomic,readonly) UIEventType     type; @property(nonatomic,readonly) UIEventSubtype  subtype; 事件产生的时间 @property(nonatomic,readonly) NSTimeInterval  timestamp; 事件传递 - hitTest:withEvent: SWIFT func hitTest(_ po

我看朴灵评注阮一峰的《JavaScript 运行机制详解:再谈Event Loop》

阮一峰和朴灵对我来说都是大牛,他们俩的书我都买过,阮老师的译作<软件随想录>和朴灵的<深入浅出node.js>.这个事情已经过了4个月了,所以我拿来讲应该也没啥问题. 这件事情是这样的,阮一峰在自己的博客写了篇文章<JavaScript 运行机制详解:再谈Event Loop>,然后朴灵看见了,发现了很多问题,然后在印象笔记又写了篇文章<[朴灵评注]JavaScript 运行机制详解:再谈Event Loop>,由于印象笔记现在已经不能访问了(尼玛也太烂了)

JavaScript 运行机制详解

JavaScript 运行机制详解——转载: 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. JavaScript的单线程,与它的用途有关.作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM.这决定了它只能是单线程,否则会带来很复杂的同步问题.比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另

javascript工具--控制台详解(转自 阮一峰博客)

javascript工具--控制台详解(转自 阮一峰博客) 大神这篇博客是写在2011年,主要介绍 "Firefox" 浏览器插件 "Firebug" 的操作,如今主流浏览器对控制台都已经提供了很好的支持.我自己用的最多是谷歌的 "chrome" 浏览器,下面也用 "chrome" 浏览器来调试. 一.显示信息的命令 console.log();  //控制台输入 网页中不会输出 console.info();  //一般信息

JavaScript对象类型详解

JavaScript对象类型详解 JavaScrtip有六种数据类型,一种复杂的数据类型(引用类型),即Object对象类型,还有五种简单的数据类型(原始类型):Number.String.Boolean.Undefined和Null.其中,最核心的类型就是对象类型了.同时要注意,简单类型都是不可变的,而对象类型是可变的. 什么是对象 一个对象是一组简单数据类型(有时是引用数据类型)的无序列表,被存储为一系列的名-值对(name-value pairs).这个列表中的每一项被称为 属性(如果是函

javascript 操作cookies详解

javascript 操作cookies详解 这段操作cookies的方法我使用很久了,但是一直一来没遇到什么问题,今天在做一个在第一个页面保存了cookies,第二个页面获取或者第三个页面获取的功能中,发现了方法的局限性,比如,第一个页面路径为 http://xxxxx/cyb-car2016/h5OfficeWorker/index,第二个页面路径为 http://xxxxx/cyb-car2016/h5AlertController/index,其中除了域名是一样之外,还有一个命名空间不一

[转]JavaScript异步机制详解

原文: https://www.jianshu.com/p/4ea4ee713ead --------------------------------------------------------------------------- 学习JavaScript的时候了解到JavaScript是单线程的,刚开始很疑惑,单线程怎么处理网络请求.文件读写等耗时操作呢?效率岂不是会很低?随着对这方面内容的了解和深入,知道了其中的奥秘.本篇文章就主要讲解一下JavaScript怎么处理异步问题. 一.同