事件

事件

JavaScript与HTML之间的交互是通过事件实现的。可以使用一些侦听器来预定事件,以便在事件发生时执行相应的代码。

事件冒泡

事件一开始时由具体的元素接收,然后向上传播到较为不具体的节点。例如我们点击一个按钮时,就会产生一个click事件,但这个按钮本身可能不能处理这个事件,事件必须从这个按钮传播出去,从而到达能够处理这个事件的代码中(例如我们给按钮的onclick属性赋一个函数的名字,就是让这个函数去处理该按钮的click事件),或者按钮的父级绑定有事件函数,当该点击事件发生在按钮上,按钮本身并无处理事件函数,则传播到父级去处理。

事件捕获

从不太具体的节点更早接收事件,而最具体的节点最后接收到事件。事件捕获的目的在于事件到达预定目标之前捕获它。即自上而下的去触发事件

事件流


“DOM2级事件”规定的事件流包括三个阶段:

  1. 事件捕获阶段
  2. 处于目标阶段
  3. 事件冒泡阶段

    上面这个图就描述了一个事件按如上这个顺序进行触发。

    如果要测试上面这个顺序,先来了解DOM2级事件处理程序

DOM2级事件处理——addEventListener

addEventListener是DOM2级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名,作为事件处理程序的函数和一个布尔值,最后这个布尔值如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。默认情况下第二个参数值为false,也就是说DOM2级事件处理程序默认是在冒泡阶段处理事件。
通过addEventListener添加的事件处理程序只能通过removeEventListener()来移除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
window.onload = function(){

var oBtn = document.getElementById('btn');
oBtn.addEventListener('click',function(){
console.log('btn处于事件捕获阶段');
}, true);
oBtn.addEventListener('click',function(){
console.log('btn处于事件冒泡阶段');
}, false);

document.addEventListener('click',function(){
console.log('document处于事件捕获阶段');
}, true);
document.addEventListener('click',function(){
console.log('document处于事件冒泡阶段');
}, false);

document.documentElement.addEventListener('click',function(){
console.log('html处于事件捕获阶段');
}, true);
document.documentElement.addEventListener('click',function(){
console.log('html处于事件冒泡阶段');
}, false);

document.body.addEventListener('click',function(){
console.log('body处于事件捕获阶段');
}, true);
document.body.addEventListener('click',function(){
console.log('body处于事件冒泡阶段');
}, false);

};


//html
<body>
<a href="javascript:;" id="btn">按钮</a>
</body>

效果
在这里插入图片描述

HTML事件处理程序

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

1
<input type = "button" onclick = "alert('click')">

这个操作通过指定onclick特性并将一些javascript代码作为它的值来定义。因此不能再其中使用未经过转义的html语法字符,例如&,””,<>。

html事件处理程序可以包含具体的动作,也可以调用其他地方定义的脚本

1
2
3
4
5
6
<script>
function showMessage(){
alert('click');
}
</script>
<input type = "button" onclick = "showMessage()">

这样指定事件处理程序有一个独到之处。首先会创建一个封装着元素属性值的函数,这个函数中有一个局部变量event,也就是事件对象

1
2
<input type = "button" onclick = "alert(event.tyep)">
//click

通过event变量,可以直接访问事件对象,在这个函数内部,this的值等于事件的目标元素

1
2
<input type = "button" value = "click me" onclick = "alert(this.value)">
//click me

关于这样动态创建的函数,在这个函数的内部,可以像访问局部变量一样访问document及该元素本身的成员。前提是这个函数使用with像这样扩展作用域链

1
2
3
4
5
6
7
function(){
with(document){
with(this){
//...
}
}
}

这样访问自己的属性就简单多了

1
2
3
4
5
6
7
8
9
10
11
<script>
function showMessage(){
with(document){
with(this){
//...
var val = value
}
alert(val)//hhh
}
</script>
<input type = "button" value = "hhh" onclick = "showMessage()">

html事件处理的缺点

时差问题:
以上面的栗子,如果在showMessage()解析之前就单击了按钮触发了事件,就会引发错误。因此,很多html事件都会被封装在try-catch中,因此上面的代码实际上是这样的

1
<input type = "button" value = "hhh" onclick = "try{showMessage()}catch(er){}">

因此就算发生这种情况,用户也看不到js的报错提示

扩展事件处理程序的作用域链在不同浏览器中会导致不同的结果

HTML和javascript代码紧耦合,不利于后期代码维护

DOM0级事件处理程序

将函数赋值给一个事件处理程序属性。首先获取一个要操作的对象的引用。每个元素都有自己的时间处理程序属性

1
2
3
4
var btn = document.getElementById('myBtn')
btn.onclick = function(){
alert('hhh')
}

注意:使用DOM0级事件处理程序被认为是元素的方法,这个时候事件处理程序是在元素的作用域中运行,换句话说这时函数的this指向当前元素

1
2
3
4
5
<input type = "button" value = "hhh" id  = "myBtn">
var btn = document.getElementById('myBtn')
btn.onclick = function(){
alert(this.id)//myBtn
}

实际上可以在事件处理程序中访问元素的任何属性和方法,以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理执行

可以删除DOM0级方法指定的事件处理程序

1
btn.onclick = null

事件对象

在触发DOM上某个事件,会产生一个事件对象,包含所有与时间有关的信息。
无论指定事件处理程序使用DOM0还是DOM2,都会传入event对象

1
2
3
4
5
6
7
8
9
var btn = document.getElementById('MmyBtn')
btn.onclick = function(){
alert(event.type)
}
//click
btn.addEventListener('click',function(){
alert(event.type)
},false)
//click

使用html事件处理程序时,也可以直接使用event变量

1
<input type = "button" onclick = "alert(event.tyep)">

event对象的一些属性和方法

在这里插入图片描述

2 级 DOM 事件标准定义的event属性

在这里插入图片描述
注意:stopPropagation() 可以立即停止事件在DOM中传播,取消进一步的事件捕获或冒泡。

1
2
3
4
5
6
7
8
var btn = document.getElementById('myBtn')
btn.onclick = function(){
alert('hhh')
event.stopPropagation()
}
document.body.onclick = function(){
alert('body click')
}

这样上面的两个事件处理程序就不会发生冲突

内存与性能

在javascript中,添加到页面上的事件处理程序将直接关系到页面的整体运行性能。因为每个函数都是对象,都会占用内存;内存中对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。

事件委托

对于“事件处理程序”过多的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序就可以管理某一类型的所有事件。
可以大量节省内存占用,减少事件注册,比如在table上代理所有td的click事件就非常棒
可以实现当新增子对象时无需再次对其绑定

移除事件处理程序

一方面可以使用事件委托减少事件处理程序,另一方面可以移除不需要的事件处理程序。
有时候,内存中会留有一些过时不用的“空事件处理程序”,这也是造成web应用程序性能降低的主要原因。
有两种情况会造成这个问题
1、从文档中移除带有事件处理程序的元素时,如果通过纯粹的DOM操作,或innerHTML,那么原来添加到元素中的事件处理程序极有可能无法当做垃圾回收。

1
2
3
4
5
6
7
8
9
10
<div id = "myDiv">
<input type = "button" value = "click me" id = "myBtn">
</div>
<script>
var btn = document.getElementById("myBtn")
btn.onclick = function(){
//某些操作
document.getElementById('myDiv').innerHTML = ''
}
</script>

上面这个例子中,点击按钮按钮消失,但问题是当它被移除时还保留着事件处理程序。在div元素上设置innerHTML把按钮移走,但是事件处理程序仍然与按钮保持着引用关系,有的浏览器(比如IE),会把元素和事件处理程序的引用都保存再内存中,不会被移除。因此这种情况下最好手动移除事件处理程序。

1
2
3
4
5
6
7
8
9
10
11
<div id = "myDiv">
<input type = "button" value = "click me" id = "myBtn">
</div>
<script>
var btn = document.getElementById("myBtn")
btn.onclick = function(){
//某些操作
btn.onclick = null
document.getElementById('myDiv').innerHTML = ''
}
</script>

这样写就确保不会占用内存,而DOM中移除按钮也做到了干净利索。
同时,在事件处理程序中删除按钮也能组织事件冒泡,因为目标元素在文档中是事件冒泡的前提

2、卸载页面
如果页面卸载之前没有清理干净事件处理程序,他们会滞留在内存中。这种情况在IE8以及更早版本IE 和部分其他浏览器问题最突出。最直观的表现就是刷新页面,卸载完页面再重新加载,每刷新一次,内存中滞留的对象数目就会增加,因为事件处理程序占用的内存并没有释放

一般来说最好的解决方案是通过onunload中移除所有事件处理程序

总结:对于以上两种问题,事件委托都能表现出不错的优势。把需要跟踪的事件处理程序数量减少,可以将解决这个问题变得简单。

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2015-2021 AURORA_ZXH
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信