前置知识:DOM结构
JS和HTML之间通过事件来进行交互。当文档或者浏览器窗口发生了特殊的交互,就是用监听器来预定事件,以便事件发生时执行响应的代码(观察者模式)。
事件流
随着浏览器的发展,浏览器开发团队遇到了一个问题:页面的哪一个部分会拥有某个特定的事件?举例来说,比如纸上有一组同心圆。把手指在同心圆上,那么手指指向的不止是一个圆,而是纸上的所有圆。所以后来浏览器开发团队认为,当你单击了某个按钮,除了单击了这个按钮,也单击了它的容器元素和整个页面。
事件流描述的是从页面中接收事件的顺序。
事件流有两种模式:分别是事件冒泡和事件捕获。
- 以下图结构为例来解释事件冒泡和事件捕获
-
布局图
示例图.PNG - 当我们点击了
line-two-center-child
这个块元素时。DOM树当中发生的事件流顺序如下图。
DOM事件流.PNG
事件捕获(event capture)
不太具体的node应该更早接收到事件,而最具体的node应该最后接收到事件。事件捕获的目的在于在事件到达预定目标之前捕获它。
在上图的布局中,绿色箭头代表的就是事件捕获发生时,DOM树中的node接收到事件的先后顺序。可以看到它是由最外层的document一层一层向内部传递事件。
事件冒泡(event bubble)
即为事件开始时从最具体的元素慢慢接收,然后逐级向上传播到较为不具体的节点。即为由最深的node向最外围的node扩散。
在上图布局中,红色箭头代表的就是事件捕获发生时,DOM树中的node接收到事件的先后顺序。可以看到它是由最内层的center-child一层一层向内部传递事件。
DOM事件流
“DOM标准事件流”:包含三个阶段事件捕获阶段、处于目标阶段、事件冒泡阶段。先后顺序是,事件捕获(可以截取事件)=>实际目标接收到事件=>冒泡阶段(可以对这个事件作出响应)
在捕获阶段,捕获不会到达具体的目标节点,到达目标节点处理事件被看作是冒泡阶段的一部分。
DOM0级事件
直接指定每个元素上面的事件处理属性。DOM0级方法指定的事件处理方法认为是元素的方法。因此,这个时候事件处理程序是在元素的作用域中运行的。this指向引用当前元素。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
console.log(this.id);//myBtn
}
DOM0级事件会在事件流的冒泡阶段被处理。
删除0级事件btn.onclick = null
DOM2级事件
addEventListener('eventName',callback,ifbubble)
:三个参数分别是事件名字符串,回调函数,布尔值。第三个参数为true,表示在捕获阶段调用事件处理程序。为false,表示在冒泡阶段调用事件处理。默认是使用冒泡。
removeEventListener()
:移除addEventListener添加的事件
2级事件和0级事件的区别:2级事件可以添加多个事件处理程序,处理顺序会按照它们的添加顺序触发,而0级事件只有最后一次添加能生效。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
console.log(1);
}
btn.onclick = function(){
console.log(2);
}
//btn点击后输出2
var btn = document.getElementById("myBtn");
btn.addEventListener('click', function(){
console.log(1);
},false)
btn.addEventListener('click', function(){
console.log(2);
},false)
// btn点击后先输出1再输出2
- 2级事件的问题:如果addEventListener()添加的回调是匿名函数,会无法通过removeEventListener()移除。
btn.addEventListener('click', function(){
console.log(2);
},false)
btn.removeEventListener('click', function(){
console.log(2);
},false);//无法删除
因为传入的匿名函数会认为是一个全新的函数。
事件对象对象(event)
在触发DOM上的某个事件的时候,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。
btn.addEventListener('click', function(event){
console.log(event.type);//"click"
},false);
<input type="button" value="click me" oclick="alert(event.type)">
event对象的主要成员
属性方法 | 类型 | 说明 |
---|---|---|
currentTarget | Element | 其事件处理程序当前正在处理的那个元素,比如,因为子node元素的方法触发了,冒泡到父node的的时候,currentTarget 指向父node |
target | Element | 事件的目标节点,也就是target一般是不变的,在哪个node触发就是哪个node |
type | String | 被触发的事件类型 |
bubbles | Boolean | 表明事件是否冒泡 |
cancelable | Boolean | 表明是否可以取消事件的默认行文 |
defaultPrevented | Boolean | 为true表明已经调用了preventDefault() |
eventPhase | Interger | 调用事件处理的阶段,1为捕获,2为处于目标,3为冒泡 |
preventDefault() | Function | 取消事件的默认行为 |
stopPropagation() | Function | 取消事件的进一步捕获或冒泡 |
在事件回调方法内部,this始终等于currentTarget
。只有在事件处理程序执行期间,event对象才会存在;一旦事件狐狸程序执行完成,event对象就会被销毁。
事件委托
在js当中,添加到页面上的eventhandler的数量会直接关系到页面的整体运行性能。因为,每个函数都是对象,会占用内存,内存中对象越多性能就越差,其次,必须事先指定所有eventhandler而导致的DOM访问次数,会延迟整个页面的交互就绪时间。
对于eventhandler过多的问题解决方案就是事件委托。
事件委托:利用了事件冒泡,只指定一个eventhandler就可以管理某一类型的所有事件。
<ul id="list">
<li id='1'></li>
<li id='2'></li>
<li id='3'></li>
</ul>
如果想要给3个li添加事件监听,从事件委托的角度来看,只需要给父元素ul添加监听器,那么3格li上的事件触发的时候,都会冒泡到ul。通过检测target对象(target就是触发事件的最底层对象)上的id可以判断具体是哪个li触发的操作。
移除EventHandler
当元素绑定eventhandler时,运行中的浏览器代码与js之间会建立一个连接。连接越多,页面执行越慢。
- 当通过纯粹的DOM操作,如
removeChild()
和replaceChild()
,或者在使用innerHTML替换页面中某一部分,绑定了eventhandler的元素被innerHTML删除了,会导致原来添加到元素中的eventhandler很有可能不被当作垃圾回收。
<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 = "process.."
//myBtn绑定了事件,但是事件执行完被清除掉了,但是onclick绑定的事件还在
}
</script>
- 在卸载页面的时候可能存在空事件处理程序。
网友评论