HTML和JS之间的交互, 就是通过事件实现的.

事件流

事件流描述的是从页面中接收事件的顺序. 有冒泡和捕获两种事件处理方式, 提出者为微软和网景.

事件冒泡

IE 的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

我们看这个例子:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Event Bubbling Example</title>
  5. </head>
  6. <body>
  7. <div id="myDiv">Click Me</div>
  8. </body>
  9. </html>

按照IE的逻辑, 你点击#myDive会经过一下历程:

div#myDiv -> body -> html -> document

13-事件 - 图1

此图形象地描述了什么是冒泡

事件捕获

与冒泡相反的就是事件捕获, 还是用上面那个HTML页面做例子:

按照事件捕获的逻辑, 你点击#myDive会经过一下历程:

document -> html -> body -> div#myDiv

13-事件 - 图2

此图形象地描述了什么是捕获

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>

结果如下:
13-事件 - 图3

事件处理程序

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


本章完