JavaScript使用侦听器来订阅事件,实现与HTML之间的交互。
DOM2级规范开始尝试标准化DOM事件。主流浏览器已全部实现了DOM2级事件模块的核心部分。IE8是最后一个仍使用其专有事件系统的主要浏览器。
一、 DOM事件流
1.1 事件流
事件流描述的是从页面接收事件的顺序。
-
事件冒泡(event bubbling)
事件开始时由最具体的元素(嵌套层次最深的那个节点)接收,然后逐层向上传播。the Element > Elements outer > body > html > document
-
事件捕获(event capturing)
事件由外层节点接收事件传递到具体节点document > html > body > Elements outer > the Element
尽管“DOM2级事件”规范要求事件应该从document对象开始传播,但IE9、Safari、Chrome、Opera和Firefox都是从window对象开始捕获事件的。在冒泡模式中也将事件一直冒泡到window对象
“DOM2级事件”规定的事件流包括三个阶段:
-
事件捕获阶段
事件从
document
到html
再到body
。这个阶段目标节点不会接收到事件 -
处于目标阶段
目标节点接收到事件,在事件处理中被看做冒泡阶段的一部分
-
事件冒泡阶段
事件又从目标节点传播回文档
即使“DOM2级事件”规范明确要求捕获阶段不会涉及事件目标,但主流浏览器高版本都会在捕获阶段触发事件对象上的事件。结果就有两个机会在目标对象上面操作事件。
IE8及更早版本不支持DOM事件流(不支持捕获)
二、 事件处理程序
click、load、mouseover等由用户或浏览器自身执行的某种动作就是事件。
而响应某个事件的函数就是事件处理程序(或事件侦听器)。
事件处理程序的名字以“on”开头:onclick、onload;为事件制定处理程序有好几种方式。
2.1 HTML事件处理程序
使用一个与相应事件处理程序同名的HTML特性来指定,指定的值应该是 能够执行的JavaScript代码,
所以调用页面其他地方定义的函数时不能只填一个函数名哦 。
<a onclick="alert('clicked!')">click me</a>
<a onclick="foo()">click me</a>
-
事件处理程序中的代码在执行时有权访问全局作用域中的任何代码
-
可以访问局部变量event(事件对象,你不必自己定义它,也不用从函数的参数列表中读取)。
-
在函数内部this值等于事件的目标元素。
<a onclick="console.log(event)">click me</a>
> MouseEvent {isTrusted: true, screenX: 39, screenY: 120, ...}
事件处理程序作用域使用with像下面这样扩展
function(){
with(document){
with(this){
}
}
}
SO,事件处理程序访问自己的属性就肥肠简单了
<a onclick="console.log(href)">click me</a>
如果当前元素是一个表单输入元素,则作用域中还会包含表单元素的入口
function(){
with(document){
with(this.form){
with(this){
}
}
}
}
这样不需要引用表单元素就能访问其他表单字段
<form action="#">
<input type="text" name="username">
<input type="button" value="submit" onclick="console.log(username.value)">
</form>
BUT,如果不是在HTML特性中直接写的执行代码,而是调用的其他地方定义的函数,那么在指定函数中是无法直接访问上述扩展的作用域的。而且this等于window(除非你用箭头函数)
<button id="btn1" onclick="console.log(id)">HTML</button> // btn1
<button id="btn2" onclick="handler()">HTML2</button>
<script>
function handler() {
console.log(id) //Uncaught ReferenceError: id is not defined
}
</script>
通过HTML指定事件处理程序最大的缺点就是HTML与JavaScript代码紧密耦合
2.2 DOM0级事件处理程序
通过JavaScript指定事件处理程序的传统方式: 将一个函数赋值给一个事件处理程序属性
每个元素(包括window和document)都有自己的事件处理程序属性,这些属性通常全部小写。
这个方法会替换这个元素上所有已存在的相应事件处理程序
//首先取得一个要操作的对象的引用
var btn = document.getElementById('btn')
//将属性值设为一个函数
btn.onclick = function(){console.log('clicked:'+this.id)}
tips:
- 使用DOM0级方法指定的事件处理程序被认为是元素的方法。因此事件处理程序是在元素的作用域内执行(函数中this引用当前元素,但没有像HTML那样为我们扩展作用域)
- 以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理
- 删除通过DOM0级方法指定的事件处理程序,只需将相应属性值设为null:
btn.onclick=null
(同样可以清除通过HTML特性设置的相应事件处理程序) - 通过HTML指定事件处理程序,则节点对象相应的事件处理属性的值就是一个包含着在HTML特性中指定代码的函数。eg.
<button id="btn1" onclick="console.log(id)">HTML</button>
<button id="btn2" onclick="handler()">HTML2</button>
<button id="btn3">DOM0</button>
<script>
function handler() {
console.log(this.id)
}
//首先取得一个要操作的对象的引用
var btn1 = document.getElementById('btn1')
var btn2 = document.getElementById('btn2')
var btn3 = document.getElementById('btn3')
//将属性值设为一个函数
btn3.onclick = function(){console.log(this.id)}
console.log(btn1.onclick) //ƒ onclick(event) {console.log('clicked:'+this.id, event, this, id)}
console.log(btn2.onclick) //ƒ onclick(event) {handler()}
console.log(btn3.onclick) //ƒ (){console.log(this.id)}
</script>
由此也可以看出为什么在HTML特性中指定外部处理函数时,在函数内无法访问扩展的作用域(直接访问节点对象属性)的原因(函数作用域)。
为我们扩展作用域的应该是 onclick(event)函数:)
2.3 DOM2级事件处理程序
“DOM2级事件”定义了两个方法来指定和删除事件处理程序:
```js
//那些不支持参数options的浏览器,会把第三个参数默认为useCapture,即设置useCapture为true
target.addEventListener('click', listener ,{
capture: Boolean,
//表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
once: Boolean,
//表示 listener 在添加之后最多只调用一次。如果是 true,
//listener 会在其被调用之后自动移除。
passive: Boolean,
//表示 listener 永远不会调用 preventDefault()。
//如果listener仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告
//添加passive参数后,touchmove事件不会阻塞页面的滚动(同样适用于鼠标的滚轮事件)
});
/**
true 是指在DOM树中,注册了该listener的元素,是否会先于它下方的任何事件
目标,接收到该事件。沿着DOM树向上冒泡的事件不会触发被指定为use capture
(也就是设为true)的listener。当一个元素嵌套了另一个元素,两个元素都对
同一个事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的
事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。进一步的解释可
以查看 事件流 及 JavaScript Event order 文档。 如果没有指定, useCapture
默认为 false 。
**/
target.addEventListener('click', listener, true)
```
tips:
- DOM2级方法添加的事件处理程序也是在其依附的元素的作用域中执行的(没有扩展作用域)
- 可以添加多个事件处理程序,按顺序触发
- 通过
addEventListener
添加的事件处理程序只能通过使用removeEventListener
来移除,移除时传入的参数与添加时使用的参数要相同。这也意味着通过addEventListener
添加的匿名函数将无法移除 - IE8不支持
2.4 IE事件处理程序
IE实现了与DOM2级中类似的两个方法:attachEvent和detachEvent
tips:
- IE8及更早版本只支持事件冒泡,所以通过attachEvent添加的事件处理程序都会被添加到冒泡阶段
-
attachEvent
第一个参数是onclick
而不是DOM中addEventListener
的click
- 事件处理程序在全局作用域中运行,因此this等于window
- 可以为一个元素添加多个事件处理程序,但不同于DOM方法,他们不是按添加他们的顺序执行,而是以相反的顺序被触发。
- 只有IE和Opera支持IE事件处理程序
FINAL
作用域 | HTML/函数 | DOM0 | DOM2 | IE |
---|---|---|---|---|
event | Y | Y | Y | Y |
this | Y/N | Y | Y | N |
this.xxx | Y/N | N | N | N |
var div1 = document.getElementById('div1')
var div2 = document.getElementById('div2')
function handler(){
var ep = ''
switch(event.eventPhase){
case 1: ep = '捕获阶段'; break;
case 2: ep = '处于目标'; break;
case 3: ep = '冒泡阶段'; break;
default: ep = 'error';
}
console.log(this.id, ep)
}
div1.addEventListener('click', handler, true)
div1.addEventListener('click', handler, false)
div2.addEventListener('click', handler, true)
div2.addEventListener('click', handler, false)
-
点击inner:
image -
点击outer
image
<= to be continued
网友评论