JS红皮书读书笔记-13-事件

TOC
  1. 1. 事件流
    1. 1.1. 事件冒泡
    2. 1.2. 事件捕获
    3. 1.3. DOM事件流
  2. 2. 事件处理程序
    1. 2.1. HTML事件处理程序
    2. 2.2. DOM0事件处理程序
    3. 2.3. DOM2事件处理程序
    4. 2.4. IE事件处理程序
    5. 2.5. 跨浏览器的事件处理程序
  3. 3. 事件对象
    1. 3.1. DOM中的事件对象
    2. 3.2. IE中的事件对象
    3. 3.3. 跨浏览器的事件对象
  4. 4. 事件类型
    1. 4.1. UI事件
    2. 4.2. 焦点事件
    3. 4.3. 鼠标滚轮事件
    4. 4.4. 键盘文本事件
    5. 4.5. 复合事件
    6. 4.6. 变动事件
    7. 4.7. HTML5事件
    8. 4.8. 设备事件
    9. 4.9. 触摸和手势事件
  5. 5. 内存和性能
    1. 5.1. 事件委托
    2. 5.2. 移除事件处理程序
  6. 6. 模拟事件
    1. 6.1. DOM中的事件模拟
    2. 6.2. IE中的事件模拟

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>

结果如下:
DOM事件流处理结果

事件处理程序

可以理解成响应某种动作的函数, 比如说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


本章完

访客评论