# 剑指前端(前端入门笔记系列)——事件

一、事件的解析

(1)事件的作用:JS控制页面的行为是由事件驱动的
(2)什么是事件?(发生的条件):JS侦测用户的操作或是页面上的一些行为
(3)发生在谁身上?(事件源):引发事件的元素
(4)发生了什么事?(事件处理函数):对事件处理的程序或函数
(5)事件的信息:事件对象event只有当事件发生的时候才会产生,只能在处理函数内部访问处理函数运行结束后自动销毁

二、事件对象简介

  在触发DOM上的某个事件时,会在函数或方法的内部产生一个事件对象event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。例如,鼠标操作导致的事件对象中,会包含鼠标位置的信息,而键盘操作导致的事件对象中,会包含与按下的键有关的信息。所有浏览器都支持event对象,但支持的方式不同。

2.1 事件对象的解析

(1)事件对象的作用:记录信息,帮助我们完成事件处理函数
(2)事件对象的特点:

  • 没有事件,就没有事件对象
  • 事件处理函数结束,事件对象被回收
  • 默认情况下,事件对象被隐藏,只能主动获得事件对象

2.2 获取

  兼容DOM的浏览器会将一个event对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0级或DOM2级),都会传入一个event对象。event的获取在IE和其他浏览器的方式不同,在IE中事件对象是window.event,其他浏览器中是事件处理函数的第一个传入的参数作为事件对象,所以兼容这里这两类浏览器的写法是:

对象.on事件 = function(eve){ 
    // eve是事件对象
    var e = eve || window.event; // 非IE||IE
    console.log(e); 
}

2.3 作用

  事件对象被获取后,都能干什么呢?它能提供以下鼠标事件常用属性:
(1)提供一些鼠标位置信息

  • 检测相对于浏览器(可视区域)的位置:clientX和clientY,也就是当鼠标事件发生时,鼠标相对于浏览器左上角的位置
  • 检测相对于文档(包括了滚走的距离)的位置:pageX和pageY,也就是当鼠标事件发生时,鼠标相对于文档左上角的位置。(IE7/8无)(类似于event.clientX和event.clientY)
  • 检测相对于屏幕的位置:screenX和screenY,也就是当鼠标事件发生时,鼠标相对于屏幕左上角的位置
  • 检测相对于事件源的位置:offsetX和offsetY,也就是当鼠标事件发生时,鼠标相对于事件发生元素左上角的位置 在这里插入图片描述

(2)提供一些键盘信息
event.keyCode 返回当前按键的ASCII码
兼容问题:var eve = eve || window.event var keyC = eve.keyCode || eve.which

2.4 默认事件的阻止

  浏览器都会有一些自己的默认事件,也就相当于自带的事件,比如:在浏览器中单击右键,会弹出一个下拉菜单;a链接的跳转,form提交时的跳转等。但有时我们不需要这些默认事件,所以需要手动去取消这些默认事件,这里又涉及到IE浏览器和其他浏览器的兼容:

if( e.preventDefault ){     
    e.preventDefault(); // 非IE 
}else{     
    window.event.returnValue = false; // IE 
}

三、事件流

  JavaScript与HTML之间的交互式通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用处理程序来预定程序,以便事件发生时执行相应的代码。这种模型支持页面的行为(JavaScript代码)与页面的外观(HTML和CSS代码)之间的松散耦合。
  浏览器开发团队在开发到第四代是,曾提出这样一个有意思的问题:页面的那一部分会拥有某个特定的事件?这个问题的考虑是基于这样一个思想:画在一张纸上的一组同心圆,如果你把你的手指放在圆心上,那么你的手指指向的不是一个圆,而是纸上的所有圆。这个思想体现在页面就是:如果你单击了一个按钮,那么单击事件不仅仅发生在按钮上,同时也发生在了这个按钮的容器元素,甚至也单击了整个页面。
  事件流这个概念被提出了,它描述的是从页面中接收事件的顺序。但是有趣的是,事件流有两种完全相反的概念:一个是事件冒泡流,一个是事件捕获流(IE不支持),两者刚好相反。

3.1 事件冒泡

  事件冒泡的思想是:事件开始时由最具体的元素(文档中嵌套层次最深的节点)接收,然后逐级向上传播到较为不具体的节点(文档)。以下面的HTML页面为例:

扫描二维码关注公众号,回复: 8663762 查看本文章
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <div></div>
    </body>
</html>

  如果单击了页面中的<div>元素,那么这个click事件会按照如下顺序传播:(1)<div>(2)<body>(3)<html>(4)document也就是说,click事件首先在<div>元素上发生,而这个元素就是我们单击的元素。然后,click事件沿DOM树向上传播,在每一级节点上都会发生,直到传播到document对象。下图展示了事件冒泡的过程:
在这里插入图片描述

3.2 事件捕获

  事件捕获的思想是:不太具体的节点应该更早的接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于事件到达预定目标之前捕获它。继续用事件冒泡的例子演示,那就是:
(1)document
(2)<html>
(3)<body>
(4)<div>
  也就是说,在事件捕获过程中,document对象首先接收到click事件,然后事件沿DOM树依次向下,一直传播到事件的实际目标,即

元素。下图展示了事件捕获的过程:
在这里插入图片描述
  由于老版本的浏览器不支持,因此很少有人使用事件捕获,所以可以多用事件冒泡,再有特殊需要时在使用事件捕获。

3.3 DOM事件流

  “DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。,下图展示了单击

元素后事件触发过程:
在这里插入图片描述
  在DOM事件流中,实际的目标(
元素)在捕获阶段不会接收到事件。这就意味着在捕获阶段,事件从document到再到后就停止了。下一阶段是“处于目标”阶段,于是事件在
上发生,并在事件处理中被看成冒泡阶段的一部分。然后,冒泡阶段发生,事件又传播回文档。
  多数支持DOM事件流的浏览器都实现了一种特定的行为:即使“DOM2级事件”规范明确要求捕获阶段不会涉及事件目标,但主流浏览器都会在捕获阶段触发事件对象上的事件。结果就是有两个机会在目标对象上面操作事件。

四、事件处理程序

  事件是用户或浏览器自身执行的某种动作。诸如:click、load和mouseover,都是事件的名字。而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序是以“on”开头,因此click事件的事件处理程序就是onclick,load事件的事件处理程序就是onload。 在W3C标准中:事件可以写在行内,但是因为结构和行为要分离,所以我们一半情况下用JS的方法来绑定事件,只有在极少数情况下,才将事件写在行内。行内事件又叫:HTML事件处理程序。为事件指定处理程序的方式有如下几种:

4.1 DOM0级事件处理程序(无兼容)

  “DOM0级事件”只能绑定一个事件处理程序,是通过JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种为事件处理程序赋值的方法虽说出现的比较早,但是因为其简单和跨浏览器的优势,仍然为所有现代浏览器所支持。要使用JavaScript指定事件处理程序,首先必须取得一个要操作的对象的引用。

  • 指定

  每个元素(包括window和document)都有自己的事件处理程序属性,这些属性通常是小写。例如:onclick。将这种属性的值设置为一个函数,就可以指定事件处理程序,例如:

<div id="box">11111</div>
<script type="text/javascript">
var oBox = document.getElementById("box");

oBox.onclick = function(){
console.log(1);
}
</script>

单击页面中的“11111”运行的结果:

1

  在此,我们通过文档对象取得了一个按钮的引用,然后为它指定了onclick事件处理程序。使用DOM0级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行,也就是,程序中的this引用当前元素。来看一个例子:


<div id="box">11111</div>
<script type="text/javascript">
var oBox = document.getElementById("box");

oBox.onclick = function(){
console.log(this.id); //"box"
}
</script>

  单击按钮显示的是元素的id,这个id是通过this.id获取的。不仅仅是id,实际上可以在事件处理程序中通过this访问元素的任何属性和方法,以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。

  • 删除

  DOM0级方法指定的事件处理程序的删除方法很简单,只需要将事件处理程序属性的值设置为null即可。

oBox.onclick = null; //删除事件处理程序

  将事件处理程序设置为null之后,再单击按钮将不会有任何动作发生。

4.2 DOM2级事件处理程序(有兼容)

  “DOM2级事件”可以重复绑定多个事件,通过该两个函数来操作事件处理程序的指定和删除,所有DOM节点都包含这两个方法,并且他们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段低啊用事件处理程序,如果是false,表示在冒泡阶段调用事件处理程序。比如说之前的例子在这应该这样处理:

4.2.1 非IE下

  • 指定
<div id="box">11111</div>
<script type="text/javascript">
var oBox = document.getElementById("box");

oBox.addEventListener("click",function(){
console.log(1);
},false)
</script>

  如果再给这个按钮绑定一个事件,那么这两个事件处理程序会按照添加它们的顺序触发,比如:

<div id="box">11111</div>
<script type="text/javascript">
var oBox = document.getElementById("box");

oBox.addEventListener("click",function(){
console.log("单击了1");
},false);
oBox.addEventListener("click",function(){
console.log("单击了2");
},false);
oBox.addEventListener("dblclick",function(){
console.log("双击了!")
})
</script>

  单击页面中的“11111”,先是触发了“单击了1”和“单击了2”,【注意】这两个事件虽然是同时显示出来,因为“单击了1”顺序靠前,所以先触发到,而双击事件是最后被触发的,直接双击,就会出现如下的结果,因为双击相当于两次快速单击,所以相当于在双击时,同时出发了两次单击和一次双击。

单击了1
单击了2
单击了1
单击了2
双击了!
  • 删除
      通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除;移除时传入的参数与添加处理程序时使用的参数相同。这也就意味着通过addEventListener()添加的匿名函数将无法移除。比如:
<div id="box">快点我</div>
<script type="text/javascript">
var oBox = document.getElementById("box");

oBox.addEventListener("click",function(){
console.log(this.id);
},false);

oBox.removeEventListener("click",function(){
console.log(this.id);
})
</script>

运行结果:

box

  虽然代码都一样,但我们都知道函数是对象,对象都是引用类型,所以这两个函数并不是一个函数,只是这两个函数实现了同一个功能。正确的做法应该是:

<div id="box">快点我</div>
<script type="text/javascript">
var oBox = document.getElementById("box");
var fn = function(){
console.log(this.id);
};
oBox.addEventListener("click",fn,false);

oBox.removeEventListener("click",fn,false);
</script>

  这样的话,两次传入的函数都是fn,所以就可以取消oBox的点击事件了

4.2.2 IE下

  • 指定
var oBox = document.getElementById("box");
oBox.attachEvent("onclick",function(){
console.log(this.id);
});

  如果再给这个按钮绑定一个事件,那么这两个事件处理程序会按照添加它们的顺序触发,比如:

<div id="box">快点我</div>
<script type="text/javascript">
var oBox = document.getElementById("box");

oBox.attachEvent("onclick",function(){
console.log(this.id);
});

oBox.attachEvent("onclick",function(){
console.log("Hello World!");
});

</script>

  这里也像之前其他浏览器的测试一样,为同一个按钮添加了两个不同的事件处理程序。不过,与DOM方法不同的是,这些事件处理程序不是以添加它的顺序执行,而是以相反的顺序被触发(IE8+是正常顺序)。

World!
Hello
  • 删除

  使用attachEvent()添加的事件可以通过detachEvent()来移除,条件时必须提供相同的参数。与DOM方法一样。这也意味着添加的匿名函数将不能被移除。不过,只要把对相同函数的引用传给detachEvent(),就可以移除相应的事件处理程序,例如:

<div id="box">快点我</div>
<script type="text/javascript">
var oBox = document.getElementById("box");
var fn = function(){
console.log("Hello");
};
oBox.attachEvent("onclick",fn);

oBox.detachEvent("onclick",fn);

</script>

4.2.3 兼容处理

  我们可以看到,DOM2级事件处理程序是有兼容的,所以我们应该寻求一种兼容的写法,下面列出了封装成函数和对象的两种方式:

  • 封装成对象的方式
var EventUtil={
////指定事件处理程序
        addHandler:function(DOM,EventType,fn){
            if(DOM.addEventListener){
                DOM.addEventListener(EventType,fn,false);
            }else if(DOM.attachEvent){
                DOM.attachEvent('on'+EventType,fn)
            }else{
                DOM['on'+EventType]=fn
            }
        },
//删除事件处理程序
        removeHandler:function(DOM,EventType,fn){
            if(DOM.removeEventListener){
                DOM.removeEventListener(EventType,fn,false)
            }else if(DOM.detachEvent){
                DOM.detachEvent('on'+EventType,fn)
            }else{
                DOM['on'+EventType]=null;
            }
        }
    }
  • 封装成两个函数的方式
//指定事件处理程序
function addEvent(obj,inci,back){
    if(obj.addEventListener){
        obj.addEventListener(inci,back);
    }else if(obj.attachEvent){
        obj.attachEvent("on" + inci,back);
    }else{
        obj["on"+inci] = back;
    }
} 
//删除事件处理程序
function removeEvent(obj,inci,back){
    if(obj.removeEventListener){
        obj.removeEventListener(inci,back,false);
    }else if(obj.detachEvent){
        obj.detachEvent("on" + inci,back);
    }else{
        obj["on"+inci] = null;
    }
}

4.3 内存和性能

  由于事件处理程序可以为现代Web应用程序提供交互能力,因此经常会出现页面中被添加了大量的处理程序,可是在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占内存;内存中的对象越多,性能就越差。其次,必须实现指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。事实上,从如何利用好时间处理程序的角度出发,还是有一些方法能够提升性能的。

4.4 事件委托

  对“时间处理程序过多”问题的解决方案就是事件委托。事件委托的思想是:利用事件冒泡原理,将绑定在多个子元素身上的相同事件,绑定在页面上现存的父元素身上。从而实现只指定一个事件处理程序,就可以管理某一类型的所有事件。

五、事件类型整理

1. 页面事件(window)
onload当文档加载完成后执行一些操作
onscroll当页面发生滚动时执行一些操作
onresize当窗口大小发生改变时执行一些操作
2. 鼠标事件(obj)
onclick 单击
ondblclick 双击
onmousedown 按下
onmouseup 抬起
onmousemove 移动
onmouseover 放上去
onmouseenter 进入(和mouseover效果相同,但是比mouseover好,不会引起冒泡)
onmouseout 离开
onmouseleave 离开(和mouseleave效果相同,但是比mouseover好,不会引起冒泡)
onmousewheel 滚轮事件
onmouseenter和onmouseleave不支持事件冒泡
3. 表单事件(obj)
onsubmit 提交
onfocus 获得焦点
onblur 失去焦点
onchange 改变文本区域的内容
4. 键盘事件
onkeydown 键盘按下
onkeyup 键盘抬起
onkeypress 按下并抬起数字字母按键

六、总结

  1. IE和DOM事件流的区别:执行顺序不一样、参数不一样、事件加不加on、this指向问题
  2. 事件就是给浏览器定义一个预处理函数,当事件触发的时候,执行函数,这就是事件。
发布了24 篇原创文章 · 获赞 43 · 访问量 9799

猜你喜欢

转载自blog.csdn.net/weixin_37844113/article/details/100822270
今日推荐