# 事件

事件流描述的是从页面中接收事件的顺序。事件冒泡即事件开始时由最具体的元素接收,然后逐渐向上传播到较为不具体的节点。事件捕获是不太具体的节点更早接收到事件,而具体节点最后接收到事件。当事件触发时,首先经历的是一个捕获过程;事件会从最外层的元素开始穿梭,逐层穿梭到最内层元素。这个穿梭过程会持续到事件抵达它目标的元素位置。此时事件流就切换到了目标阶段--事件被目标元素所接收。然后事件会回弹,进入冒泡阶段

DOM2级规定事件流包括三个阶段:事件捕获,处于目标,事件冒泡阶段。在DOM事件流中,实际的目标在捕获阶段不会收到事件。

# 事件处理程序

TIP

  • 利用on绑定多个事件后面的会覆盖前面的,利用addEventListener就不会了
  • on是直接将函数赋值给事件,addEventListener则是利用多个参数
window.onload = function(){
    var box = document.getElementById("box");
    box.onclick = function(){
        console.log("我是box1");
    }
    box.onclick = function(){
        box.style.fontSize = "18px";
        console.log("我是box2");
    }
}

box.addEventListener("click",function(){
    console.log("box");
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

第三个参数默认为false表示事件按照事件冒泡的执行顺序进行。true为捕获顺序进行。也可以传入一个对象option

option: {
    capture : Boolean // 和上面单个参数一样
    once: Boolean // 表示listener在添加之后最多只调用一次。如果是 true,listener会在其被调用之后自动移除。
    passive: Boolean //表示listener永远不会调用preventDefault()。如果listener仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
}
1
2
3
4
5

# HTML事件处理程序

<input type="button" onclick="alert('event.type')>
1

# DOM0级事件处理程序

这种模型不会传播,所以没有事件流的概念,但是现有的浏览器支持以冒泡的方式实现,可以在网页中直接定义监听函数,也可以通过js属性来监听函数。这种方式是所有浏览器都兼容的。因此,事件处理程序会在元素的作用域中运行,即 this 等于元素。

var btn = document.getElementById('mybtn');
btn.onclick=function() {
    var event = window.event;
    alert(event.type) // click
}

// 移除事件处理程序
btn.onclick = null
1
2
3
4
5
6
7
8

# DOM2级事件处理程序

用于处理指定和删除事件处理程序的操作:addEventListener()removeEventListener()。接收三个参数,第一个参数为事件名,第二个参数事件执行函数,第三个为布尔值(true代表捕获阶段调用事件处理程序,false表示冒泡阶段,默认为后者,两个阶段只能有一个执行。)

var btn = document.getElementById('mybtn');
btn.addEventListener('click', function() {
    console.log('这里表示冒泡阶段调用事件处理程序')
}, false)
1
2
3
4

DOM0 方式类似,这个事件处理程序同样在被附加到的元素的作用域中运行。使用 DOM2 方式的主要优势是可以为同一个事件添加多个事件处理程序。多个事件处理程序以添加顺序来触发

# IE事件处理程序

在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。这种模型可以添加多个监听函数,会按顺序依次执行。 IE中类似的事件处理程序操作为attachEventdetachEventIE8 以下只支持冒泡。第一个参数是完整事件,例如onclick,而不是事件名click

WARNING

  1. attachEvent() 添加的事件处理程序会添加到冒泡阶段。
  2. IE 中使用 attachEvent() 与使用 DOM0 方式的主要区别是事件处理程序的作用域。使用 DOM0 方式时,事件处理程序中的 this 值等于目标元素。而使用attachEvent() 时,事件处理程序是在全局作用域中运行的,因此 this 等于 window
  3. 与使用 addEventListener() 一样,使用 attachEvent() 方法也可以给一个元素添加多个事件处理程序。不过,与 DOM 方法不同,这里的事件处理程序会以添加它们的顺序反向触发。
  4. 与使用 DOM 方法类似,作为事件处理程序添加的匿名函数也无法移除。但只要传给 detachEvent() 方法相同的函数引用,就可以移除。

# 事件对象

# DOM中的事件对象

通过event变量,可以直接访问事件对象。在事件函数内部,this等于事件的目标对象(currentTarget的值),而target则只包含事件的实际目标。如果直接将事件处理程序指定给目标元素,则this/currentTargettarget包含相同的值

<ul>
    <li>vue</li>
    <li>css</li>
    <li>js</li>
</ul>
1
2
3
4
5
let ul = document.getElementsByTagName('ul')[0]
ul.onclick = function(e) {
    console.log(this,e.currentTarget,e.target) // ul,ul,li
}
1
2
3
4

在事件冒泡阶段,e.currentTargete.target是不相等的,但是在事件的目标阶段两者相等

属性/方法 类型 读/写 说明
bubbles Boolean 只读 表明事件是否冒泡
cancelable Boolean 只读 表明是否可以取消默认行为
currentTarget Element 只读 事件处理程序正在处理的那个元素添加监听事件的对象
defaultPrevented Boolean 只读 true表示已经执行了取消默认行为事件 preventDefault()
detail Integer 只读 与事件相关细节信息
eventPhase Integer 只读 调用事件处理程序的阶段,1为捕获,2为处于目标,3为冒泡
preventDefault() Function 只读 取消默认行为,只有cancelabletrue才可以
target Element 只读 事件目标指向触发事件监听的对象
type String 只读 被触发事件类型
stopPropagation() Function 只读 取消事件进一步捕获/冒泡,只有bubblestrue有效

stopImmediatePropagation 同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件。

# IE中的事件对象

  1. DOM0添加事件处理程序时。event作为window的一个属性
var btn = document.getElementById('mybtn');
btn.onclick=function() {
    var event = window.event;
    alert(event.type) // click
}
1
2
3
4
5
  1. 使用attachEvent则将会有一个event对象作为参数传入事件处理程序函数中。
属性/方法 类型 读/写 说明
cancelBubble Boolean 读/写 默认值为false,但将其设置为true就可以取消事件冒泡(与DOM中的stopPropagation()方法的作用相同
returnValue Boolean 读/写 默认值为true,但将其设置为false就可以取消事件的默认行为(与DOM中的preventDefault()方法的作用相同
srcElement Element 只读 事件的目标(与DOM中的target属性相同
type String 只读 被触发的事件类型

由于IE不支持事件捕获,因而只能取消事件冒泡;但stopPropagation()可以同时取消事件捕获和冒泡

# 事件委托(事件代理)

事件委托利用的是事件冒泡,最适合采用事件委托技术的事件包括:click/mousedown/mouseup/keydown/keyup/keypress

TIP

  1. 节省内存
  2. 不需要给子节点注销事件

每当事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的js代码之间就会建立一个连接。这种连接越多页面执行就会越慢。采用事件委托技术减少连接数量。另外不需要事件的时候要移除事件处理程序。一般做法是在页面卸载之前通过onunloaded事件处理程序移除所有事件处理程序。

document.getElementById('father-id').onclick = function(event) {
    event = event || window.event
    let target = event.target || event.srcElement
    if(target.nodeName.toLowerCase() === 'xxx')  {

    }
}
1
2
3
4
5
6
7

# 事件类型

# UI事件

  1. load:当页面完全加载后在window上触发,当所有框架加载完毕在框架集上触发,当图像加载完毕时在<img>元素上面触发。
  2. 浏览器窗口被调整到一个新的大小时会触发resize事件。在window上触发。

# 鼠标事件

  1. mouseenter:在鼠标光标从元素外部首次移动元素范围之内时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。相对应mouseleave
  2. mouseout:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。又移入的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素。
  3. mouseover:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发。
  4. 除了mouseenter/mouseleave其余鼠标事件均可冒泡,也可以被取消,不过会影响浏览器默认行为。
  5. mousemove:当鼠标指针在元素内部移动时重复地触发。
  6. mouseup:在用户释放鼠标按钮时触发。
  7. click按下鼠标/键盘回车触发
  8. mousewheel/wheel鼠标滚轮事件。

TIP

  • 坐标位置 clientX/clientY表示事件发生时鼠标指针在视口中的水平/垂直坐标 pageX/pageY,表示事件在页面什么位置发生(没有滚动条时和上面两个属性相同) screenX/screenY:表示鼠标在屏幕的位置 offsetX/offsetY(FF浏览器为layerX/layerY):鼠标点击位置相对于触发事件对象的水平/垂直距离

  • 鼠标按钮 只有在主鼠标按钮被单击/或键盘回车键被按下时才会触发click事件,因此检测按钮信息并不是必要的。但对于mousedown/mouseup事件来说其event对象存在一个button属性,表示按下/释放的按钮。0表示主鼠标按钮,1表示中间鼠标按钮,2表示次鼠标按钮。

  • mousewheel包含一个wheelDelta属性,当用户向前滚动滚轮时wheelDelta120的倍数,相反为-120的倍数。Firefox则有一个DOMMouseScroll类似的事件,而滚轮信息保存在detail属性中,当鼠标向前则为-3的倍数,相反为3的倍数。

# 键盘事件

DOM3推荐使用keydown/keyup

# HTML5事件

  • contextmenu自定义菜单事件
  • DOMContentLoaded事件

TIP

windowload事件会在页面中的一切都加载完毕时触发,但这个过程可能会因为要加载的外部资源过多而浪费周折。而DOMContentLoaded事件则在形成完成的DOM树之后就会触发,不理会图像,js文件,css文件或其他资源是否已经下载完毕。要处理该事件,可以为document/window添加相应的事件处理程序(尽管这个事件会冒泡到window,但它的目标实际上是document

在不支持该事件的浏览器,建议在页面加载期间设置一个时间为0毫秒的超时调用。

  • hashchange事件

以便在url参数列表(及'#'后面的所有字符串)发生变化时通知开发人员。由window对象来调用这个事件处理程序,此时的event对象包含两个属性:oldURL/newURL

# 触摸事件/手势事件

# 模拟事件

# DOM的事件模拟

可以在document对象上使用createEvent()方法创建事件对象。接收一个表示要创建的事件类型字符串参数。

  • MouseEvents模拟鼠标事件,返回的对象有initMouseEvent()方法。
  • KeyboardEvent创建键盘事件,有initKeyEvent()方法。

# IE事件模拟

document.createEventObject()方法模拟事件。

# 实现一个跨浏览器通用事件侦听函数

const EventUtils = {
  // 分别使用dom0||dom2||IE方式 来绑定事件
  // 添加事件
  addEvent: 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;
    }
  },

  // 移除事件
  removeEvent: 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;
    }
  },

  // 获取事件目标
  getTarget: function(event) {
    return event.target || event.srcElement;
  },

  // 获取 event 对象的引用,取到事件的所有信息,确保随时能使用 event
  getEvent: function(event) {
    return event || window.event;
  },

  // 阻止事件(主要是事件冒泡,因为 IE 不支持事件捕获)
  stopPropagation: function(event) {
    if (event.stopPropagation) {
      event.stopPropagation();
    } else {
      event.cancelBubble = true;
    }
  },

  // 取消事件的默认行为
  preventDefault: function(event) {
    if (event.preventDefault) {
      event.preventDefault();
    } else {
      event.returnValue = false;
    }
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52