HTML和JS之间的交互, 就是通过事件实现的.
事件流
事件流描述的是从页面中接收事件的顺序. 有冒泡和捕获两种事件处理方式, 提出者为微软和网景.
事件冒泡
IE 的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
我们看这个例子:
<!DOCTYPE html>
<html>
<head>
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>
按照IE的逻辑, 你点击#myDive会经过一下历程:
div#myDiv -> body -> html -> document
此图形象地描述了什么是冒泡
事件捕获
与冒泡相反的就是事件捕获, 还是用上面那个HTML页面做例子:
按照事件捕获的逻辑, 你点击#myDive会经过一下历程:
document -> html -> body -> div#myDiv
此图形象地描述了什么是捕获
DOM事件流
“DOM2级事件"规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。
一句话总结: DOM事件流会先触发捕获,再触发冒泡
我们看一个例子:
<!DOCTYPE html>
<html lang="en" id="html">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body id="body">
<div id="mydiv">click me</div>
<script>
html.addEventListener('click', ()=>{
console.log('html.capture')
}, true)
html.addEventListener('click', ()=>{
console.log('html.bubble')
}, false)
body.addEventListener('click', ()=>{
console.log('body.capture')
}, true)
body.addEventListener('click', ()=>{
console.log('body.bubble')
}, false)
mydiv.addEventListener('click', ()=>{
console.log('mydiv.capture')
}, true)
mydiv.addEventListener('click', ()=>{
console.log('mydiv.bubble')
}, false)
</script>
</body>
</html>
结果如下:
事件处理程序
可以理解成响应某种动作的函数, 比如说click事件自然对应onClick. 等等
HTML事件处理程序
即在HTML里面直接使用诸如onClick之类的事件, 现在已经不推荐使用, 主要原因如下:
- 代码耦合
- 未加载完所有资源就激活了事件导致报错
DOM0事件处理程序
我们来看一个例子:
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(this.id); //"myBtn", 注意这里的this
};
//btn.onclick = function(){
// console.log('dom02次绑定会覆盖')
//};
这就是通常见到的DOM0事件处理程序, 它的优势在于兼容IE浏览器, 缺点在于不能对同一个元素绑定相同的事件, 否则后面的会覆盖前面的事件处理程序
DOM2事件处理程序
“DOM2 级事件"定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()和 removeEventListener()。所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。
那么DOM0那个例子可以改写成:
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id); //注意这里的this
}, false);
btn.addEventListener("click", function(){
alert('repeat'); // 'repeat'
}, false);
DOM2能按顺序正常触发绑定的重复事件处理程序.
我们知道函数是引用类型的实例, 即使是匿名函数也不等于另一个匿名函数, 所以使用removeEventListener的时候, 第二个参数必须是一个函数名才有意义, 否则无效.
IE事件处理程序
IE也实现了和DOM2类似的两个方法, 分别是attachEvent和detachEvent.但只支持冒泡.所以只有两个参数, 我们看下面的例子:
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){ // 注意第一个参数是有on前缀的
alert(this === window); //注意这里的this不是某个html元素的引用
});
btn.attachEvent("onclick", function(){
alert('btn.attachEvent2'); // 它也是可以重复添加相同事件处理程序的
});
值得注意的是, attachEvent添加多个同类事件处理程序, 是逆序执行的. 同样, detachEvent的第二个参数也是要一个函数名才有意义.
跨浏览器的事件处理程序
既然要兼容IE和标准浏览器:
var EventUtil = {
addHandler: function(element, type, handler){
if (element.addEventListener){
element.addEventListener(type, handler, false);
} else if (element.attachEvent){
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler){
if (element.removeEventListener){
element.removeEventListener(type, handler, false);
} else if (element.detachEvent){
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
};
兼容思路:
DOM2 -> IE -> DOM0
事件对象
在触发 DOM 上的某个事件时,会产生一个事件对象 event,这个对象中包含若所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。例如,鼠标操作导致的事件对象中,会包含鼠标位置的信息,而键盘操作导致的事件对象中,会包含与按下的键有关的信息。所有浏览器都支持 event 对象,但支持方式不同
DOM中的事件对象
兼容 DOM 的浏览器会将一个 event 对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0 级或 DOM2 级),都会传入 event 对象。来看下面的例子:
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event);
};
btn.addEventListener("click", function(event){
alert(event.type);
}, false);
可以用for-in循环枚举出事件对象的熟悉和方法,这里只列几个常用的属性和方法:
- preventDefault(): 阻止默认行为, 例如禁止a标签跳转href
- currentTarget: 略
- stopPropagation(): 阻止事件继续捕获或者冒泡
- type: 事件类型
IE中的事件对象
IE中DOM0事件的话, 则用window.event访问
var btn = document.getElementById("myBtn");
btn.onclick = function(){ // DOM0事件
var event = window.event;
alert(event.type); //"click"
};
IE中DOM2事件则与标准浏览器相同
跨浏览器的事件对象
主要兼容思路:
var event = event ? event : window.event
事件类型
UI事件
常见UI事件如下:
- load
- unload
- abort
- error
- select
- resize
- scroll
焦点事件
常见焦点事件如下:
- blur
- focus
鼠标滚轮事件
常见鼠标滚轮事件如下:
- click
- dblclick
- mousedown
- mouseenter
- mouseleave
- mousemove
- mousemout
- mousemover
- mouseup
- mousewheel
键盘文本事件
常见键盘文本事件如下:
- keydown
- keypress
- keyup
- textInput
复合事件
这个符合事件的存在主要解决输入法输入的时候”有效输入”的问题, 可以看这篇文章, 会更好理解:
https://github.com/julytian/issues-blog/issues/15
- compositionstart
- compositionupdate
- compositionend
变动事件
通常指页面的某个节点在以下集中情况触发的事件:
- 删除节点
- 插入节点
对应的事件:
- DOMSubtreeModified
- DOMNodeInserted
- DOMNodeRemove
- DOMNodeInsertedIntoDoucmnet
- DOMNodeRemovedFromDoucmnet
- DOMAttrModified
- DOMCharacterDataModified
由于在日常开发中用得少, 这部份的讲解略过
HTML5事件
- contextmenu
- beforeunload
- DOMContentLoaded
- readystatechange: 它存在四种状态
- loading
- loaded
- interactive
- complete
- pageshow/pagehide
- hashchange: 挂载在window对象上, 主要用于URL变化检测
设备事件
主要设备事件如下:
- orientationchange
- deviceorientation:
- devicemotion: 包含以下属性
- acceleration: 这个属性可以实现”摇一摇”的功能
- accelerationIncludingGravity
- interval
- rotationRate
有关这部分的知识可以参考 https://imweb.io/topic/56ab279be39ca21162ae6c75 , 会更为清晰.
触摸和手势事件
常用触摸事件:
- touchstat
- touchend
- touchmove
- touchcancel
常用手势事件:
略
此节内容可以查看 https://segmentfault.com/a/1190000004332409
内存和性能
假如你要对某个ul下的2个li添加点击事件 , 正常的思路自然会先获取一个li, 再添加事件处理程序, 同理再对第二个li进行类似处理. 这看起来也没什么问题.
假如ul下有100个li呢? 逐一对li添加事件处理程序可不显示, 而且非常耗费内存和性能.
事件委托
针对上面描述的情况, 我们对li的父元素做一次绑定即可:
var ul = document.getElementById('ul');
ul.addeventListener('click',function (event) {
switch (event.target.id) { //传入li的id
case 'id1':
// ...
break;
case 'id2':
// ...
break;
//...
default:
// ...
break;
}
},false)
这样性能会提高, 可维护性也会更好
移除事件处理程序
如果事件用完之后不需要再用了, 最好手动移除事件处理程序
模拟事件
DOM中的事件模拟
略
IE中的事件模拟
略
如需学习, 请查阅此处 https://segmentfault.com/a/1190000004339133
本章完