1、事件流:
事件流描述了页面接收事件的顺序。IE
支持事件冒泡流,而网景支持事件捕获流。
1-1 事件冒泡:
IE
事件流被称为事件冒泡。事件被定义为从最具体的元素开始触发,向上传播至没那么具体的元素(文档),即从内向外
1-2 事件捕获:
网景提出另外一种名为事件捕获的事件流。意思是最不具体的节点应该先收到事件,而最具体的节点最后收到事件,即从外向内
1-3 DOM
事件流:
DOM
事件流分为3个阶段:事件捕获、到达目标和事件冒泡。事件捕获最先发生,为提前拦截事件提供了可能。然后就是目标阶段,实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个阶段响应事件。
2、事件处理程序:
事件意味着用户或浏览器执行的某种动作,如cilck
,load
,mouseover
。为响应事件而调用的函数被称为事件处理程序(或事件监听器),事件处理程序的名字以"on
"开头,因此click
事件的处理程序叫onclick
,同理,有onload
等等
2-1 HTML
事件处理程序
<button onclick="console.log('clicked')">按钮</button>
<!-- 或者 -->
<button onclick="showMessage()">按钮</button>
<script>
function showMessage() {
console.log('clicked')
}
</script>
2-2 DOM0
事件处理程序
<button id="button"></button>
<script>
let btn = document.getElementById('button')
// 添加事件处理程序
btn.onclick = function() {
console.log(this.id) // button
}
// 移除事件处理程序
btn.onclick = null
</script>
要使用这种方式,必须先取得要操作对象的引用。像这样为事件处理程序赋值时,所赋函数被视为元素的方法,因此,事件处理程序会在元素的作用域中运行,即this
等于元素。在事件处理程序里通过this
可以访问元素的任何属性和方法。以这种方式添加事件处理程序是注册在事件流的冒泡阶段的。
2-3 DOM2
事件处理程序
事件处理程序赋值addEventListener()
事件处理程序移除removeEventListener()
这两个方法暴露在所有DOM
节点上,它们接收3个参数:事件名、事件处理函数和一个布尔值。true
表示在捕获阶段调用事件处理函数,false
(默认值)表示在冒泡阶段调用事件处理函数。
let btn = document.getElementById('button')
btn.addEventListener('click', () => {
cocnsole.log(this.id); // button this指向元素本身
}, false)
优势:可以为同一个事件添加多个事件处理程序。多个事件处理程序以添加的顺序来触发。
注意点:
- 通过
addEventListener()
添加事件处理程序只能使用removeEventListener()
并传入与添加时同样的参数来移除。不支持匿名函数。 - 大多数情况下,事件处理程序会被添加到事件流的冒泡阶段,主要是因为跨浏览器兼容性好。把事件处理程序添加到事件流的捕获阶段通常用于在事件到达其指定目标之前拦截事件。如不需要拦截,则不使用事件捕获。
2-4 IE
事件处理程序
事件处理程序赋值attachEvent()
事件处理程序移除detachEvent()
这两个方法接收2个参数:事件处理程序的名字on + 'type'
和事件处理函数。只会添加到冒泡阶段。
let btn = document.getElementById('button')
btn.attachEvent('onclick', function() {
console.log(this) // window 由于事件处理程序是在全局作用域下运行的,所以this指向window
})
优势:也是可以添加多个事件处理程序。不过这里多个事件处理程序以添加的顺序反向触发。
注意点:通过attachEvent()
添加事件处理程序只能使用detachEvent()
来移除,需提供相同的参数。不支持匿名函数。
2-5 跨浏览器事件处理程序❗❗❗
<button id="btn" onclick="showMessage()">click me</button>
<script>
let EventUtil = {
addHandler: function(ele, type, handler) {
if (ele.addEventListener) {
ele.addEventListener(type, handler, false);
} else if (ele.attachEvent) {
ele.attachEvent('on' + type, handler);
} else {
ele['on' + type] = handler;
}
},
removeHandler: function(ele, type, handler) {
if (ele.removeEventListener) {
ele.removeEventListener(type, handler, false);
} else if (ele.detachEvent) {
ele.detachEvent('on' + type, handler);
} else {
ele['on' + type] = null;
}
}
}
let btn = document.getElementById('button');
let handler = function() {
console.log('clicked')
}
EventUtil.addHandler(btn, 'click', handler)
EventUtil.removeHandler(btn, 'click', handler)
</script>
3、事件对象
在DOM
中发生事件时,所有相关信息都会被收集并存储在一个名为event
的对象中。这个对象包含一些基本信息,如导致事件的元素、发生的事件类型。
3-1 DOM
事件对象(含DOM0
和DOM2
)
在DOM
合规的浏览器中,event
对象是传给事件处理程序的唯一参数。不管以哪种方式(DOM0
/DOM2
)指定的事件处理程序,都会传入这个event
对象。
let btn = document.getElementById('button');
btn.onclick = function(e) {
console.log(e.type); // click
}
btn.addEventListener("click", (e) => {
console.log(e.type); // click
}, false)
需要掌握的事件对象的方法:
preventDefault()
:用于阻止特定事件的默认动作。如取消a标签在被点击时导航到href
属性指定的URL
这个默认行为。
<a id="myLink" href="###">链接</a>
<script>
let link = document.getElementById('myLink');
link.onclick = function(e) {
e.preventDefault();
}
</script>
stopPropagation()
:用于立即阻止事件流在DOM
结构中传播,取消后续的事件捕获或冒泡(既可取消捕获,也可取消冒泡)。如直接在添加到按钮的事件处理程序中调用stopPropagation()
,可以阻止document.body
上注册的事件处理程序执行。
let btn = document.getElementById('button');
btn.onclick = function(e) {
console.log('clicked')
e.stopPropagation()
}
document.body.onclick = function(e) {
console.log('body clicked')
}
eventPhase
:用于确定事件流当前所处的阶段。1是捕获阶段 2是目标阶段 3是冒泡阶段
target
:事件目标元素
3-2 IE
事件对象
与DOM
事件对象不同,IE
事件对象可以基于事件处理程序被指定的方式以不同的方式来访问:
如果事件处理程序是使用DOM0
方式来指定的,则event
对象是window
的一个属性
let btn = document.getElementById('button');
btn.onclick = function() {
let event = window.event;
console.log(event.type)
}
如果事件处理程序是使用IE
事件处理程序,即attachEvent()
方式来指定的,则event
对象是作为参数传给处理函数
let btn = document.getElementById('button');
btn.attachEvent("click", (event) => {
console.log(event.type)
})
如果事件处理程序是使用HTML
属性方式来指定的,则event
对象可以通过变量event
来访问
<button onclick="console.log(event.type)">click</button>
需要掌握的事件对象的属性或方法:
returnValue
:用于取消给定事件默认的行为。
btn.onclick = function() {
window.event.returnValue = false; // 此属性设置为false, 为阻止默认动作
}
cancelBubble
:用于阻止事件冒泡。(由于IE8
及更早的版本不支持捕获阶段,所以只会取消冒泡)
btn.onclick = function() {
console.log('clicked')
window.event.cancelBubble = true; // 此属性设置为true, 为阻止事件冒泡
}
document.body.onclick = function() {
console.log('body clicked')
}
srcElement
:事件目标元素
3-3 跨浏览器事件对象❗❗❗
<button id="btn">click me</button>
<script>
var EventUtil = {
addHandler: ...,
removeHandler: ...,
// 获取事件对象
getEvent: function(event) {
return event ? event : window.event
},
// 获取事件目标
getTarget: function(event) {
return event.target || event.srcElement
},
// 阻止默认事件
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
// 阻止事件冒泡或事件捕获
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation()
} else {
event.cancelBubble = true;
}
}
}
</script>
4、事件类型
这里不一一列举。具体查看红宝书17章第四节
5、内存与性能
在JavaScript
中,页面中事件处理程序的数量与页面整体性能直接相关。原因有很多,首先,每个函数都是对象,都占用内存空间,对象越多,性能越差。其次,为执行事件处理程序所需访问DOM
的次数会造成整个页面交互的延迟。
5-1 事件委托(事件冒泡的应用)
”过多事件处理程序“的解决方案是使用事件委托。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一个类型的事件。比如有一个li
列表,需要给每一项添加click
事件,如果量少的话可能没有感觉什么变化,但是如果量大的话,上万个,几十万个,那挨个添加事件处理程序显然不是很🆗的做法。解决办法是利用事件冒泡,使用事件委托,只要给所有元素共同的祖先节点添加一个事件处理程序即可解决。
事件委托的优点:
- 只要页面渲染出可点击的元素,就可以无延迟地起作用
- 设置页面事件处理程序时省时省力,节省
DOM
的引用 - 减少整个页面所需的内存,提升整体性能
5-2 删除事件处理程序
把事件处理程序指定给元素后,在浏览器代码和负责页面交互的JavaScript
代码之间就建立了联系。这种联系建立得越多,页面性能越差。除了上面说的使用事件委托来限制这种连接之外,还应该及时删除不再使用的事件处理程序。
导致这个问题的原因主要有两个:
- 删除带有事件处理程序的元素时:因为该元素被删除后,其实它仍然关联着一个事件处理程序,很有可能元素的引用和事件处理程序的引用都会残留在内存中。所以为了保险起见,我们如果知道某个元素会被删除,最后在删除它之前手动移除它的事件处理程序。
- 页面卸载时:同样的道理,页面卸载时,其实页面上的事件处理程序仍然被引用着,没有清理,则残留在内存中。之后,浏览器每次加载和卸载页面,内存中残留对象的数量都会增加,这是因为事件处理程序不会被回收。所以,举个比较常用的例子,一般我们在
vue
页面中使用事件监听,都要在页面卸载之前也就是beforeDestroy
生命周期中移除事件监听。
网友评论