收集的一些关于JavaScript事件的部分内容

1 什么是javascript事件?

可以被JavaScript侦测到的行为。

网页中的每个元素都可以产生某些可以触发JavaScript函数的事件。比方说,我们可以在用户点击某按钮时产生一个onClick事件来触发某个函数。事件在HTML页面中定义。

事件举例:

  1. 鼠标点击
  2. 页面或图像载入
  3. 鼠标悬浮于页面的某个热点之上
  4. 在表单中选取输入框
  5. 确认表单
  6. 键盘按键

注意:事件通常与函数配合使用,当事件发生时函数才会执行。

2.添加事件的方式

2.1 绑定事件的方式

主要分为两种在html文档中绑定在js代码中绑定

2.1.1 方式1

HTML的DOM元素支持onclick、onblur等以on开头属性,我们可以直接在这些属性值中编写javascript代码。当点击div的时候,下面的代码会弹出div的ID:

收集的一些关于JavaScript事件的部分内容

这种做法很显然不好,因为代码都是放在字符串里的,不能格式化和排版,当代码很多的时候很难看懂。这里有一点值得说明:onclick属性中的this代表的是当前被点击的DOM对象,所以我们可以通过this.id获取DOM元素的id属性值。

2.1.2 方式2

当代码比较多的时候,我们可以在onclick等属性中指定函数名。

收集的一些关于JavaScript事件的部分内容

跟上面的做法相比,这种做法略好一些。值得一提的是:事件处理函数中的this代表的是window对象,所以我们在onclick属性值中,通过this将dom对象作为参数传递。

2.1.3 方式3

在JS代码中通过dom元素的onclick等属性

收集的一些关于JavaScript事件的部分内容

这种做法this代表当前的DOM对象。还有一点:这种做法只能绑定一个事件处理函数,后面的会覆盖前面的。

2.1.4 方式4

IE下使用attachEvent/detachEvent函数进行事件绑定和取消。

attachEvent/detachEvent兼容性不好,IE6~IE11都支持该函数,但是FF和Chrome浏览器都不支持该方法。而且attachEvent/detachEvent不是W3C标准的做法,所以不推荐使用。在IE浏览器下,attachEvent有以下特点。

a) 事件处理函数中this代表的是window对象,不是dom对象。

收集的一些关于JavaScript事件的部分内容

b) 同一个事件处理函数只能绑定一次。

收集的一些关于JavaScript事件的部分内容

虽然使用attachEvent绑定了2次,但是函数a只会调用一次。

c)不同的函数对象,可以重复绑定,不会覆盖。 ​ var dom = document.getElementById("outestA"); dom.attachEvent('onclick',function(){ alert(1); }); dom.attachEvent('onclick',function(){ alert(1); });

收集的一些关于JavaScript事件的部分内容

匿名函数和匿名函数是互相不相同的,即使代码完全一样。所以如果我们想用detachEvent取消attachEvent绑定的事件处理函数,那么绑定事件的时候不能使用匿名函数,必须要将事件处事函数单独写成一个函数,否则无法取消。

2.1.5 方式5

使用W3C标准的addEventListener和removeEventListener。 这2个函数是W3C标准规定的,FF和Chrome浏览器都支持,IE6/IE7/IE8都不支持这2个函数。不过从IE9开始就支持了这2个标准的API。

收集的一些关于JavaScript事件的部分内容

a) 事件处理函数中this代表的是dom对象,不是window,这个特性与attachEvent不同。

收集的一些关于JavaScript事件的部分内容

b) 同一个事件处理函数可以绑定2次,一次用于事件捕获,一次用于事件冒泡。

收集的一些关于JavaScript事件的部分内容

如果绑定的是同一个事件处理函数,并且都是事件冒泡类型或者事件捕获类型,那么只能绑定一次。

收集的一些关于JavaScript事件的部分内容

c) 不同的事件处理函数可以重复绑定,这个特性与attachEvent一致。

2.2 动态绑定事件

在js代码中动态获得节点然后添加事件的方式被称为:动态绑定事件

类似于之前的:方式3、方式4、方式5这几种方式。

2.3 事件指派方式

事件指派方式在现在分为两种:传统指派方式现代指派方式

2.3.1 传统指派方式

传统指派方式只需要添加事件名和需要执行的函数。

例如: var odiv = document.getElementById("div1"); odiv.onmouseover = function(){ alert('over'); }

点击

方式3也属于传统指派方式。

2.3.2 现代指派方式

现代指派方式主要是添加事件监听.

例如:

收集的一些关于JavaScript事件的部分内容

其中第三个参数:

收集的一些关于JavaScript事件的部分内容

方式4、方式5也属于现代指派方式

3.事件处理函数的执行顺序

方式1、方式2和方式3都不能实现事件的重复绑定,所以自然也就不存在执行顺序的问题。方式4和方式5可以重复绑定特性,所以需要了解下执行顺序的问题。如果你写出依赖于执行顺序的代码,可以断定你的设计存在问题。所以下面的顺序问题,仅作为兴趣探讨,没有什么实际意义。直接上结论:addEventListener和attachEvent表现一致,如果给同一个事件绑定多个处理函数,先绑定的先执行。下面的代码我在IE11、FF17和Chrome39都测试过。

收集的一些关于JavaScript事件的部分内容

当点击outA的时候,会依次打印出1、2、3、4。这里特别需要注意:我们给outA绑定了多个onclick事件处理函数,也是直接点击outA触发的事件,所以不涉及事件冒泡和事件捕获的问题,即addEventListener的第三个参数在这种场景下,没有什么用处。如果是通过事件冒泡或者是事件捕获触发outA的click事件,那么函数的执行顺序会有变化。

例:

收集的一些关于JavaScript事件的部分内容

上述代码中出现了一个新的问题:两个div都同时具有不同的事件,但是两个div具有包含关系,点击里层div时却也触发了外层div的事件,这个被我们称为--冒泡事件。

3.1 冒泡事件

冒泡事件:当页面上使用事件进行冒泡时,先触发子类,在触发父类元素。

IE浏览器默认是冒泡,谷歌火狐也是冒泡

IE只支持冒泡。标准的DOM浏览器(谷歌、火狐)即支持冒泡,又支持事件的捕获

如果事件没有特殊的定义,其使用时期都默认会在冒泡阶段。

4.事件冒泡和事件捕获

事件冒泡和事件捕获很好理解,只不过是对同一件事情的不同看法,只不过这2种看法都很有道理。 我们知道HTML中的元素是可以嵌套的,形成类似于树的层次关系。比如下面的代码:

收集的一些关于JavaScript事件的部分内容

如果点击了最内侧的outC,那么外侧的outB和outC算不算被点击了呢?很显然算,不然就没有必要区分事件冒泡和事件捕获了,这一点各个浏览器厂家也没有什么疑义。假如outA、outB、outC都注册了click类型事件处理函数,当点击outC的时候,触发顺序是A-->B-->C,还是C-->B-->A呢?如果浏览器采用的是事件冒泡,那么触发顺序是C-->B-->A,由内而外,像气泡一样,从水底浮向水面;如果采用的是事件捕获,那么触发顺序是A-->B-->C,从上到下,像石头一样,从水面落入水底。

事件冒泡见下图:

事件捕获见下图:

一般来说事件冒泡机制,用的更多一些,所以在IE8以及之前,IE只支持事件冒泡。IE9+/FF/Chrome这2种模型都支持,可以通过addEventListener((type, listener, useCapture)的useCapture来设定,useCapture=false代表着事件冒泡,useCapture=true代表着采用事件捕获

收集的一些关于JavaScript事件的部分内容

使用的是事件冒泡,当点击outC的时候,打印顺序是3-->2-->1。如果将false改成true使用事件捕获,打印顺序是1-->2-->3。

5.DOM事件流

在页面上执行一个点击工作,触发多个元素执行响应的动作被称为--事件流

页面上不止一个元素可以响应相同的事件。

DOM事件流:将事件分为三个阶段:捕获阶段、目标阶段、冒泡阶段。先调用捕获阶段的处理函数,其次调用目标阶段的处理函数,最后调用冒泡阶段的处理函数。这个过程很类似于Struts2框中的action和Interceptor。当发出一个URL请求的时候,先调用前置拦截器,其次调用action,最后调用后置拦截器。

收集的一些关于JavaScript事件的部分内容

当点击outC的时候,依次打印出capture1-->capture2-->target-->bubble2-->bubble1。到这里是不是可以理解addEventListener(type,handler,useCapture)这个API中第三个参数useCapture的含义呢?useCapture=false意味着:将事件处理函数加入到冒泡阶段,在冒泡阶段会被调用;useCapture=true意味着:将事件处理函数加入到捕获阶段,在捕获阶段会被调用。从DOM事件流模型可以看出,捕获阶段的事件处理函数,一定比冒泡阶段的事件处理函数先执行。

6.再谈事件函数执行先后顺序

在DOM事件流中提到过:

收集的一些关于JavaScript事件的部分内容

我们在outC上触发onclick事件(这个是目标对象),如果我们在outC上同时绑定捕获阶段/冒泡阶段事件处理函数会怎么样呢?

收集的一些关于JavaScript事件的部分内容

点击outC的时候,打印顺序是:capture1-->capture2-->target2-->target1-->bubble2-->bubble1。由于outC是我们触发事件的目标对象,在outC上注册的事件处理函数,属于DOM事件流中的目标阶段。目标阶段函数的执行顺序:先注册的先执行,后注册的后执行。这就是上面我们说的,在目标对象上绑定的函数是采用捕获,还是采用冒泡,都没有什么关系,因为冒泡和捕获只是对父元素上的函数执行顺序有影响,对自己没有什么影响。如果不信,可以将下面的代码放进去验证

收集的一些关于JavaScript事件的部分内容

至此我们可以给出事件函数执行顺序的结论了:捕获阶段的处理函数最先执行,其次是目标阶段的处理函数,最后是冒泡阶段的处理函数。目标阶段的处理函数,先注册的先执行,后注册的后执行。

7.阻止事件冒泡和捕获

7.1 event对象

事件发生时产生的对象。

事件的对象,封装操作事件源的一些信息

在IE中获取 ​ //IE div1.onclick = function(){ var e = window.event; }

在DOM中获取

收集的一些关于JavaScript事件的部分内容

7.1.1 event的属性和方法

Dom和IE的event相同点。

  1. 获取事件类型:e.type
  2. 获取键盘码:keydown/keyup
  3. 检测是否按下e.shiftKey,e.altKey,e.ctrlKey
  4. 获取客户坐标:e.clientX,e.clientY
  5. 获取屏幕坐标:e.screenX,e.screenY

Dom和IE的event不同点

7.2 阻止事件冒泡/捕获

默认情况下,多个事件处理函数会按照DOM事件流模型中的顺序执行。如果子元素上发生某个事件,不需要执行父元素上注册的事件处理函数,那么我们可以停止捕获和冒泡,避免没有意义的函数调用。前面提到的5种事件绑定方式,都可以实现阻止事件的传播。由于第5种方式,是最推荐的做法。所以我们基于第5种方式,看看如何阻止事件的传播行为。IE8以及以前可以通过 window.event.cancelBubble=true阻止事件的继续传播;IE9+/FF/Chrome通过event.stopPropagation()阻止事件的继续传播。

<style>
    #outA{
        width:400px; 
        height:400px; 
        background:#CDC9C9;
        position:relative;
    }
    #outB{
        height:200px; 
        background:#0000ff;
        top:100px;
        position:relative;
    }
    #outC{
        height:100px; 
        background:#FFB90F;
        top:50px;
        position:relative;
    }
</style>
<script>
    window.onload = function(){
        var outA = document.getElementById("outA");  
        var outB = document.getElementById("outB");  
        var outC = document.getElementById("outC");  
        
        // 目标
        outC.addEventListener('click',function(event){
            alert("target");
            event.stopPropagation();
        },false);
        // 事件冒泡
        outA.addEventListener('click',function(){
            alert("bubble");
        },false);
        // 事件捕获
        outA.addEventListener('click',function(){
            alert("capture");
        },true);
    };
 </script>
<body>
    <div id="outA">
        <div id="outB">
            <div id="outC"></div> 
        </div>
    </div>
</body>

当点击outC的时候,之后打印出capture-->target,不会打印出bubble。因为当事件传播到outC上的处理函数时,通过stopPropagation阻止了事件的继续传播,所以不会继续传播到冒泡阶段。

最后再看一段更有意思的代码:

<style>
    #outA{
        width:400px; 
        height:400px; 
        background:#CDC9C9;
        position:relative;
    }
    #outB{
        height:200px; 
        background:#0000ff;
        top:100px;
        position:relative;
    }
    #outC{
        height:100px; 
        background:#FFB90F;
        top:50px;
        position:relative;
    }
</style>
<script>
    window.onload = function(){
        var outA = document.getElementById("outA");  
        var outB = document.getElementById("outB");  
        var outC = document.getElementById("outC");  
        
        // 目标
        outC.addEventListener('click',function(event){
            alert("target");
        },false);
        // 事件冒泡
        outA.addEventListener('click',function(){
            alert("bubble");
        },false);
        // 事件捕获
        outA.addEventListener('click',function(){
            alert("capture");
            event.stopPropagation();
        },true);        
        
    };
 </script>
<body>
    <div id="outA">
        <div id="outB">
            <div id="outC"></div> 
        </div>
    </div>
</body>

执行结果是只打印capture,不会打印target和bubble。神奇吧,我们点击了outC,但是却没有触发outC上的事件处理函数,而是触发了outA上的事件处理函数。

示例1代码:事件代码/冒泡事件1--内外相同事件.html
示例2代码:事件代码/冒泡事件2--内外不同事件.html

示例主要是同学们更清楚冒泡事件。

8.其它事件举例

事件类型:鼠标事件/键盘事件/HTML事件

8.1 鼠标事件

  1. click
  2. dbclick
  3. mousedown
  4. mouseout
  5. mouseover
  6. mouseup
  7. mousemove
  8. 页面上所有元素均支持鼠标事件。事件发生顺序

单击:mousedown,mouseup,click 双击:mousedown,mouseup,click,mousedown,mouseup,click,doubleclick

如有有click和onclick疑问的

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<button id="btn3" onclick="change()">dd</button>
<button id="btn4">ee</button>
<script>
    var btn1 = document.getElementById("btn4");
    var btn2 = document.getElementById("btn3");
    btn1.onclick = function(){
        btn2.click();
    };
    function change(){
        alert("onclick");
    }
</script>
</body>
</html>

onclick是绑定事件,告诉浏览器在鼠标点击时候要做什么。

click本身是方法作用是触发onclick事件,只要执行了元素的click()方法,就会触发onclick事件。

如上诉代码所示,当点击'ee'按钮时,会触发'dd'的onclick事件(正常来说得按'dd'按钮才触发'dd'的onclick事件),原因就是因为

var btn1 = document.getElementById("btn4");
btn1.onclick = function(){
        btn2.click();
};

点击'ee'按钮时,代码内部调用了'dd'的click()方法,从而触发了'dd'的onclick事件

8.2 键盘事件

常见事件

  1. keydown
  2. keypress
  3. keyup
  4. 通常在输入框上实现键盘事件。返回false表示不响应该事件

事件发生顺序

字符键:keydown,keypress,keyup 非字符键:keydown,keyup

8.3 HTML事件

常见事件

  1. load,unload
  2. abort,error
  3. select
  4. change
  5. submit
  6. reset
  7. resize
  8. scroll
  9. focus
  10. blur

8.3.1 onload 和 onUnload事件

当用户进入或离开页面时就会触发 onload 和 onUnload 事件。

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script>
        function load(){
            alert("启动~")
        }
        function goodbye(){
            alert("关闭~");
        }
    </script>
</head>
<body onload="load()" onunload="goodbye()">
    <h1>欢迎访问我的首页</h1>
    <p>请关闭窗口,或按 F5 刷新页面。</p>
</body>
</html>

也可以写为:

window.onload = function(){
    //内容...
}

onload 事件常用来检测访问者的浏览器类型和版本,然后根据这些信息载入特定版本的网页。

onload 和 onUnload 事件也常被用来处理用户进入或离开页面时所建立的 cookies。

例如,当某用户第一次进入页面时,你可以使用消息框来询问用户的姓名。姓名会保存在 cookie 中。当用户再次进入这个页面时,你可以使用另一个消息框来和这个用户打招呼:"Welcome John Doe!"。