小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
在学习JavaScript过程中,肯定会学到页面交互,在交互过程中我会给HTML元素节点绑定一些事件来完成交互,那么这是如何实现的呢?
事件
什么叫事件?事件如何产生?
事件就是可以背JavaScript侦听到的用户行为。比如鼠标点击、图片加载完成、鼠标进入某个元素等事件,这些事件发生后与事件处理函数相配合,来实现交互功能。DOM事件一般分为HTML事件、DOM0级事件和DOM2级事件。
事件流
事件流指从页面中接收事件的顺序,也可理解为事件在页面中传播的顺序。
在HTML页面中,DOM元素呈树级结构分布,也可以理解为同心圆,点击某个元素后,也相当于点击了他的祖先及元素,那么事件是如何先后触发传播的呢。在IE中,它认为是从下到上进行传播,从目标元素开始触发,到根结点结束,即冒泡事件流;而Netscape认为是从根结点开始触发到目标节点结束,即捕获事件流,两个公司有着完全相反的的事件流。
在DOM2级事件流中一般分为事件捕获阶段、目标阶段和事件冒泡阶段。
事件对象
Event事件对象是用来记录一些事件发生时的相关信息的对象,但事件对象只有事件发生时才会产生,并且只能是事件处理函数内部访问,在所有事件处理函数运行结束后,事件对象就被销毁!
常见属性和方法有:
- target 事件触发的节点
- currentTarget 事件绑定的节点
- stopPropagation() 冒泡机制下,阻止事件的进一步往上冒泡
- preventDefault() 取消事件的默认操作,比如链接的跳转或者表单的提交,主要是用来阻止标签的默认行为
在IE8及以前本版之中,通过设置属性注册事件处理程序时,调用的时候并未传递事件对象,需要通过全局对象window.event来获取。
function getEvent(event) {
event = event || window.event;
}
在IE浏览器上面是event事件是没有preventDefault()这个属性的,所以在IE上,我们需要设置的属性是returnValue
window.event.returnValue=false
stopPropagation()也是,所以需要设置cancelBubble,cancelBubble是IE事件对象的一个属性,设置这个属性为true能阻止事件进一步传播。
event.cancelBubble=true
事件处理程序
事件处理程序就是响应某个事件的函数,简单地来说,就是函数。我们又把事件处理程序称为事件侦听器。事件处理程序是以"on"开头的,比如点击事件的处理程序是"onclick",事件处理程序大概有以下5种。
HTML事件处理函数
HTML事件处理函数就是直接写在HTML元素上的,例如:、下面例子,点击按钮后,事件触发,浏览器弹出提示。
<button onclick="alert('html事件处理函数')">html事件处理函数</button>
当我们要实现一个复杂功能时,我们可以定义函数,在函数里处理,就变成这样了
<script>
function alertHtml() {
alert('html事件处理函数')
}
</script>
<button onclick="alertHtml()">html事件处理函数</button>
这样写的话大家有没有觉得很耦合,修改函数后可能html和js都要修改,行为和表现没有相分离,不够好,所以我们有了DOM0级事件。
DOM0级事件处理程序
先来看看DOM0级事件怎么使用
<button id="btn">DOM0级事件处理程序</button>
<script>
var btn = document.getElementById('btn');
btn.onclick = function () {
alert('DOM0级事件处理程序')
}
btn.onclick = function () {
alert('DOM0级事件处理程序2')
}
</script>
可以看到,我们是先获取了 button 这个对象,然后给他的onclick属性赋值了一个方法,事件触发后直接该方法,避免与HTML耦合。但缺点也很明显,就是一个元素的一种事件,只能绑定最后一个事件处理函数,前面的会被覆盖掉。
在DOM0级事件中的事件流是冒泡事件流,在button触发后,他的腹肌div的事件也会被触发,大家可以试一试。
DOM2级事件处理程序
针对DOM0级事件存在的问题,DOM2级事件进行了改进,新增了addEventListener和removeEventListener来监听事件和取消事件。这两个方法都接受三个参数:
- type 事件类型的字符串
- listener 事件处理函数
- useCapture true/false,说明该事件是在捕获阶段还是冒泡阶段触发,默认false冒泡阶段
- options
var btn = document.getElementById('btn');
var parent = document.getElementById('parent')
function parentFunc() {
console.log('parent ===> DOM2级事件处理程序')
}
function btnFunc() {
console.log('DOM2级事件处理程序')
}
parent.addEventListener('click', parentFunc)
btn.addEventListener('click', btnFunc)
// DOM2级事件处理程序
// parent ===> DOM2级事件处理程序
如果我们将addEventListener第三个参数指定为true的时候会在捕获阶段触发
<script>
parent.addEventListener('click', parentFunc, true)
btn.addEventListener('click', btnFunc, true)
// parent ===> DOM2级事件处理程序
// DOM2级事件处理程序
</script>
IE事件处理程序
IE是个大难题,在IE9以前是不支持addEventListener方法的,而是使用attachEvent和detachEvent这两个函数的,他们接受两个参数
- type 事件类型的字符串
- listener 事件处理函数
和addEventListener不同的是,这个的type需要加上on,并且只支持冒泡事件流,其他和DOM2级事件一样使用。
<button id="btn">点击</button>
<script>
var btn=document.getElementById("btn");
btn.attachEvent('onclick',hello);
btn.detachEvent('onclick',hello);
function hello(){
alert("hello");
}
</script>
这里事件触发的顺序不是添加的顺序而是添加顺序的相反顺序。
使用 attachEvent 方法有个缺点,this 的值会变成 window 对象的引用而不是触发事件的元素。
跨浏览器的事件处理程序
再看到上面几种事件触发后,是不是觉得很麻烦呢,不同的浏览器要用不同的方法,难道我们写代码的时候要写这么多冗余代码嘛,存不存在一种跨浏览器的事件处理程序呢,答案是没有,但是我们可以自己实现一个。
const EventUtil = {
addEvent(ele, type, handler) {
if (ele.addEventListener) {
ele.addEventListener(type, handler)
} else if (ele.attachEvent) {
ele.attachEvent(`on${type}`, handler)
} else {
ele[`on${type}`] = handler
}
},
removeEvent(ele, type, handler) {
if (ele.removeEventListener) {
ele.removeEventListener(type, handler)
} else if (ele.detachEvent) {
ele.detachEvent(`on${type}`, handler)
} else {
ele[`on${type}`] = null
}
},
getEvent(event) {
return event || window.event
},
target(event) {
return event.target || event.srcElement
},
stopPropagation(event) {
if (event.stopPropagation) {
event.stopPropagation()
} else {
event.cancelBubble = true
}
},
perventDefault(event) {
if (event.perventDefault) {
event.perventDefault()
} else {
event.returnValue = false
}
}
}
事件委托
事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
有这么一个需求,点击某个li后执行一个的操作,那么我该怎么做呢,每一个li都绑定一个事件?那么我们新添加、修改、删除li标签的时候是不是也要去处理对应的事件处理函数呢?是不是很冗余很麻烦,那么我们应该怎么做呢,没错,就是我们说的事件委托来实现,只需要在他们公共的父级绑定一个事件就好了,可以根据e.target拿到触发元素,可以通过自定义属性或者nodeName等属性知道是哪个li触发的,那么我们的代码是不是就很简洁了。如下:
<ul id="btn">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var btn = document.getElementById('btn');
function btnFuncBubble(e) {
console.log('事件委托 ==> ', e.target) // IE浏览器用event.srcElement
}
btn.addEventListener('click', btnFuncBubble)
</script>
到这里我们关于DOM事件模型就结束了,也希望能讲清楚了一些。