DOM事件相关内容
当用户与浏览器发生的一些交互时, 如果希望去获得用户行为, 就需要借助事件来完成. 事件部分内容在 JS中重要性不言而喻.
罗列需要了解与事件相关的知识如下: 这也是面试中遇到的问题.
- DOM 事件的级别
- DOM 事件模型
- DOM 事件流
- DOM 事件处理程序
- 描述DOM事件捕获(冒泡)的具体流程
- Event对象常见的应用
- 自定义事件
事件级别
- DOM0
- DOM2
- DOM3
DOM 事件模型
- 事件冒泡
- 事件捕获
什么是事件流
要想明白事件流,必须先懂的这几个知识点
- 事件冒泡
- 事件捕获
先来看一个有趣的问题, 这是 4
代浏览器(IE4)开发团队遇到一个的问题:
What part of the Webpage owns a specific event?
页面的哪一部分会拥有某个特定的事件?
要想明白这个问题可以想象成在一个页面上画了一组同心圆, 当手指放在中间时,它不仅在一个圆圈内,而且在所有的圆圈内。
如下图所示:
这就是浏览器事件的工作原理, 当你点击一个按钮时, 不仅单击按钮,还单击包含的容器和整个页面。
事件生命周期, 分为三个阶段: capturing
(捕获), target
(目标), bubbling
(冒泡). 而这三个阶段也是构成事件流基本部分.
这种处理思想, 在现在流行 NodeJS
后端框架 Koa
对中间件的处理模式也是基于这种, 熟悉Koa
或许对这洋葱图
并不会陌生:
先来熟悉事件的模型:
Javascript Events: Event bubbing (事件冒泡)
事件冒泡: 既事件开始由最具体的元素接收,然后逐级向上传播最后到达 Document 对象 或 window 上.
先来看一个简单的示例, 代码如下:
<!DOCTYPE HTML>
<html>
<head>
<title>......</title>
</head>
<body>
<div id="demo"> Press here.</div>
</body>
</html>
var target = document.getElementById("demo");
window.addEventListener("click", function(){
console.log("window bubbling");
});
document.addEventListener("click", function(){
console.log("document bubbling");
});
document.documentElement.addEventListener("click", function(){
console.log("html bubbling");
});
document.body.addEventListener("click", function(){
console.log("body bubbling");
});
target.addEventListener("click", function(){
console.log("target bubbling");
});
控制台打印输出如下:
"target bubbling"
"body bubbling"
"html bubbling"
"document bubbling"
"window bubbling"
当一个div
被点击, 这点击事件发生的顺序如下:
- div
- body
- html
- Document
- Window (现在浏览器中, IE9+, Firfox, Chrome 等)
从执行顺序来说, click事件
首先在 div
元素上触发, 然后沿着DOM Tree 向上传播, 在路径上的每个节点上触发,直到它到达文档对象(或Window对象)。
Javascript Events: Event Capturing (捕获)
事件捕获
是另外一种事件流模型, 最先由 Netscape Browser 引入.
根据上面的的模型, 刚好与前面冒泡相反. 根据该模型,最不特定(最外层)的节点首先接收事件,而最特定(目标元素)的节点最后接收事件.
它设计的目标就是在事件到达目标之前,事先进行拦截.
参考前面的示例, 修改下监听方式, 代码修改如下:
var target = document.getElementById("demo");
window.addEventListener("click", function(){
console.log("window Capturing");
}, true);
document.addEventListener("click", function(){
console.log("document Capturing");
}, true);
document.documentElement.addEventListener("click", function(){
console.log("html Capturing");
}, true);
document.body.addEventListener("click", function(){
console.log("body Capturing");
}, true);
target.addEventListener("click", function(){
console.log("target Capturing");
}, true);
打印的结果:
"window bubbling"
"document bubbling"
"html bubbling"
"body bubbling"
"target bubbling"
当点击 div
元素, 按照如下顺序来进行广播事件.
- Window (现在浏览器中, IE9+, Firfox, Chrome 等)
- Document
- Document
- body
- div
注意:
DOM0
级中默认就是使用冒泡的方式, 不支持捕获的. 所以在DOM2
级中通过addEventListener
提供的第三个参数来控制使用哪种事件流来处理.
Javascript Events: DOM Event Flow (事件流)
根据上面两种模型, 可以总结完整事件流应该向如下:
DOM Level 2 Events 指定的事件流模型分为三个阶段:
- Event Capturing Phase (事件捕获阶段)
- At the target (目标阶段)
- Event Bubbling Phase (事件冒泡阶段)
从上面流程图中, 首先发生的是 事件捕获阶段
为截获事件提供了机会, 再到目标阶段
然后进入 事件冒泡阶段
, 可以在这个阶段对事件进行响应.
引用
前面的例子, 点击 DIV
元素时, 事件将按照上图顺序进行触发.
这就是完整的事件流, 内容看起来挺多的, 实际上一句话就概述事件流.
用来描述事件发生顺序(页面接收事件顺序).
事件处理方式
通过前面的知识点, 事件
就是表示用户或浏览器自身执行某种动作. 例如: click
, dbclick
, load
, unload
, mouseover
,mouseout
, mouseenter
,mouseleave
等等, 这些都是事件的名字
. 而响应并处理某个事件的函数称为 事件处理程序
.
常见事件处理方式包括如下几种:
- HTML 事件处理程序
- DOM0 级处理程序
- DOM2 级处理程序
HTML 事件处理程序
直接来看个简单示例:
<input type="button" onclick="alert('<Clicked')"/>
这种模式, 可以理解为 CSS 行内样式
,
<div style="color: #ccc;"></div>
直接在 HTML 元素上绑定相应事件名, 并指定对应的事件处理程序. 从前面语法来说, 事件处理程序是一个函数
, 上面传递是一个语句, 也就是说默认执行时, JS引擎会进行相应处理. 等价于这种方式:
<input type="button" onclick="(function(){alert('Clicked')})()"/>
如果把事件处理函数直接以这种内联值
的方式提供, 应该注意不能值中指定未经转义的HTML语法字符
例如: 和号(&) 、双引号("") 等等, 否则会解析出错.
如上面示例,如果想使用双引号, 必须这么处理.
<input type="button" onclick="alert("Clicked")"/>
这种对于简单语句还行, 除了提供元素属性值的方式, 那么有木有其它方式咧? 答案:当然有 , HTML事件处理程序可以调用在页面其它地方定义的脚本.
示例如下:
<input type="button" onclick="handleClick()"/>
<script>
function handleClick(){
alert("Clicked");
}
</script>
通过这种方式指定事件处理程序具有一些特别的地方.
- 前面讲述事件处理程序引擎在执行时, 默认封装函数. 在封装的函数中包含一个局部变量
event
, 也就是事件对象.
<input type="button" onclick="alert(event)"/>
// => 等价
<input type="button" onclick="(function(){var e = event; alert(e);})()"/>
- 该函内部
this
指针表示是当前的目标对象
<input type="button" onclick="alert(this)"/>
// this === [object HTMLInputElement] === input元素
这样可以通过 this
来获取目标元素相关的内容了. 比如取值
<input type="button" onclick="alert(this.value)"/>
<!--或简写成这样-->
<input type="button" value="Click Me" onclick="alert(value)"/>
为什么可以简写方式, input 对象是存在于当前函数的作用域链中(scope). 为了调试方式, 把上面方式作如下改变
<input type="button" value="Click Me" onclick="(function aa(){debugger;console.log(value)})()"/>
其实理解上面那句话, 我们可以借助开发这工具来协助理解:
- 从执行栈(Call Stack)可以看出, 证明前面说的默认会创建封装函数 .
- 从 Scope 作用链中可以看出, 前面说的
input
对象会被添加在当前匿名函数执行的作用域链上.
根据开发工具来看, 我们是可以模拟引擎帮做的事情, 伪代码如下:
function () {
with(document) {
with(input){
// dosomething
console.log(value);
}
}
}
本质通过 with
来扩展作用域.
上面把基本内容说, 那么这种通过HTML事件处理程序有什么问题么 ?
- 时差问题
事件处理程序必须优先元素加载, 有可能DOM加载出来, 用户就开始操作, 此时事件处理程序可能未加载导致报错
- 前面提及扩展程序的作用域链可能在不同浏览器中兼容不一样,导致程序出现错误
- 视图和行为偶合在一起,也就是说事件处理程序修改相应JS部分、HTML部分都需要去修改.
引用前面的示例:
<input type="button" onclick="handleClick()"/>
<script>
function handleClick(){
alert("Clicked");
}
</script>
如果用户想把函数名为: "onClick", 这时是不是需要同时去修改.
DOM0 级事件处理程序
通过 JavaScript 来指定事件的处理程序, 也就是将一个事件处理程序赋值给一个元素属性(事件类型).
语法
element.onclick = function(){}
先看个简单示例:
<!--html属性方式来指定事件处理程序-->
<input type="button" onclick="handleClick()"/>
<script>
function handleClick(){
alert("Clicked");
}
</script>
<!--先来通过 DOM0 级方式来指定-->
<script>
var target = document.querySelector("input");
target.onclick = function (){
alert("Clicked");
}
</script>
上述就是 DOM0 级事件处理程序应用.
使用 DOM0级
不同于 HTML事件处理程序
优点在于:
- 把视图和行为进行解耦.
- 更容易让人理解(作用域的各种问题)
DOM0 级添加事件有哪些特点
由于通过 target.onclick
这种方式是不是很熟悉, object.property
, 那么意味这添加事件处理程序(方法) 属于当前 DOM元素
的, 相应其内部 this
指向当前元素.
<input type="button" value="ClickMe"/>
<script>
var target = document.querySelector("input");
target.onclick = function (){
console.log(this.value);
}
</script>
打印输出结果为:
"ClickMe"
从结果来看,是符合前面说的.
大家是否记得前面说事件流
提及事件两个阶段冒泡
和 捕获
, 那么通过 DOM0级别
添加的事件会发生那个阶段 ? 冒泡
通过简单示例来看看:
<input type="button" value="ClickMe"/>
<script>
var target = document.querySelector("input");
target.onclick = function (){
console.log("input bubbling");
}
document.body.onclick = function(){
console.log("body bubbling");
}
</script>
打印输出结果:
"input bubbling"
"body bubbling"
细心读者发现, 在讲解 事件捕获
时提及了, 事件捕获是 DOM2
级才进行引入的. 也验证现在说法.
移除事件处理程序
简单粗暴, 也只能这样.
target.onclick = null;
同一个事件名添加多个处理程序
DOM0级中, 使用onclick
方式添加事件处理程序是不支持添加多个, 假如有这样场景需要自己去扩充, 现在浏览器中基本使用 DOM级非常少了, 所以不必担心.
简单实现如下:
var target = document.querySelector("input");
function handleOne() {
console.log("handle one");
}
function handleTwo() {
console.log("handle two");
}
function addEvent(eventName, target, handle) {
var map = target.map ? target.map : target.map = {};
if (typeof target.onclick === "function") {
target.map[eventName].push(handle);
} else {
target.onclick = function () {
map[eventName].every(function (fn) {
return fn();
});
};
if (!map[eventName]) {
map[eventName] = [];
}
map[eventName].push(handle);
}
}
function removeEvent(eventName, target, handle) {
var map = target.map;
var list = [];
if(!map){return;}
if(list = map[eventName]) {
var index = list.indexOf(handle);
list.splice(index, 1);
}
}
addEvent("onclick", target, handleOne);
addEvent("onclick", target, handleTwo);
removeEvent("onclick", target, handleOne)
两个小细节:
为什么迭代循环时为什么使用 every
, 而不是用 forEach 、map、some; 这里先预留一个终止执行队列里面的条件, every
只有每一个数组元素满足条件时才会往下执行, 否则直接终止循环.
因为添加时直接把事件处理程序添加在一个数组中, 删除时只能通过函数引用; 所以在 addEvent
时不能通过匿名函数的方式. 否则无法被移除.
兼容性
所有浏览器都支持
DOM2 级事件处理程序
在DOM2级事件
定义了两个方法, 用来指定和删除事件处理程序的操作, 分别是 addEventListener
和 removeEventListener
.
语法:
target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
target.removeEventListener(type, listener[, options]);
target.removeEventListener(type, listener[, useCapture]);
- type
表示事件的类型的字符串
- listener
事件处理程序必须是一个实现
EventListener
接口的对象, 或 一个函数
. 两者实质都是用来接收并处理触发的事件. -
options 可选
主要跟
listener
相关的- capture
Boolean
表示该 listener 事件处理程序是否在捕获阶段被触发. - once
Boolean
表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。 - passive:
Boolean
, 设置为true时, 表示 listener 永远不会调用 preventDefault(). 如果 listener 仍然调用了这个函数, 客户端将会忽略它并抛出一个控制台警告.
- capture
- useCapture 可选
默认值: false 表示在冒泡阶段调用事件处理函数.
小提示:
从参数签名来看options
与useCapture
是不能同时配置, 这就是为什么options
中会提供capture
的原因. 这样其实带来另外一个好处, 可以更精确控制
到每一个事件处理程序的触发时机
先通过一个简单的示例来把上面知识点应用一下,示例如下:
<input type="button" value="ClickMe"/>
<script>
var target = document.querySelector("input");
//添加多个事件处理程序
target.addEventListener("click", function(){
console.log("one handle");
});
target.addEventListener("click", function(){
console.log("two handle");
});
// 在捕获阶段执行
target.addEventListener("click", function(){
console.log("capture handle");
}, {
capture: true
});
// 只执行一次
target.addEventListener("click", function(){
console.log("once handle");
}, {
once: true
});
window.addEventListener("click", function(){
console.log("window");
}, false);
</script>
控制台依次输出:
// 第一次点击时
"one handle"
"two handle"
"capture handle"
"once handle"
"window"
// 第二次点击时
"one handle"
"two handle"
"capture handle"
"window"
从输出来看:
- 默认是在
冒泡阶段
来触发事件处理程序 (listener); 需要捕获阶段拦截可以通过把useCapture
改为 false. - 使用
DOM2级
默认支持针对同一个事件类型添加多个处理程序.DOM0 级
不支持的. - 事件触发的顺序按照添加顺序执行.
在讲述语法时, listener
可以是一个实现 EventListener
接口的对象. 上面指定 listener
时我们可以使用如下方式:
<input type="button" value="ClickMe"/>
<script>
var target = document.querySelector("input");
target.addEventListener("click", {
handleEvent:function(){
console.log("handleEvent");
}
});
</script>
一般推荐使用前者;
事件处理程序执行的作用域
事件处理程序中 this
指向当前对象(input)
<input type="button" value="ClickMe"/>
<script>
var target = document.querySelector("input");
target.addEventListener("click", function (){
console.log(this.value);
});
</script>
控制台输出:
"ClickMe"
移除事件处理程序
移除事件处理程序通过 removeListener
方法. 从语法来看, 传入参数与添加时传入的参数一样, 也就是说事件处理程序(函数)就不能为匿名函数, 否则无法移除
错误的使用方式:
<input type="button" value="ClickMe"/>
<script>
var target = document.querySelector("input");
// 添加
target.addEventListener("click", function (){
console.log("handleListener");
});
// 移除
target.removeEventListener("click", function (){
console.log("handleListener");
});
</script>
控制台输出:
"handleListener"
正确使用方式:
<input type="button" value="ClickMe"/>
<script>
var target = document.querySelector("input");
function handleListener(){
console.log("handleListener");
}
// 添加
target.addEventListener("click", handleListener);
// 移除
target.removeEventListener("click", handleListener);
</script>
总结:
- 指定事件类型时,使用
click
而不是onclick
(click类型为例) - 允许支持同一个事件名添加多个处理程序, 执行顺序为添加顺序.
- 事件处理程序依附的对象, 作为处理函数执行时的作用域.
- 移除事件处理程序时, 不能使用匿名函数.
兼容性:
IE9、Firefox、Safari、Chrome和 Opera 支持DOM2级事件处理程序
从兼容性来看,那么在IE中有没有类似 addEventListener 和 removeEventListener 事件处理程序? 有
IE 中添加事件处理程序
IE实现了类似 DOM 中类似的两个方式: attachEvent()
, detachEvent()
. 这两个方法默认接收两个参数.
语法格式(Non-standard):
attachEvent(type, listener)
detachEvent(tyoe, listener)
先来看个具体示例(IE中运行):
<button id="dispatch">执行</button>
<button id="remove">移除事件处理程序</button>
<script>
var dispatchBtn = document.getElementById("dispatch");
var removeBtn = document.getElementById("remove");
function handleListener(){
console.log("handleListener");
console.log(this.id);
}
// 添加
dispatchBtn.attachEvent("onclick", handleListener);
// 移除
removeBtn.attachEvent("onclick", function(){
// 移除
dispatchBtn.detachEvent("onclick", handleListener);
})
</script>
控制台输出结果:
// 点击 "移除事件处理程序按钮之前"
"handleListener"
"undefined"
// 点击之后, 再执行没有输出(事件处理程序被移除)
注意:
- 事件类型名称(type) 和 DOM0级一样需要指定完整属性名
onclick
- 移除事件处理程序时, 传入参数必须一样
- attachEvent 和 detachEvent 不支持捕获, 也就是说在被添加到冒泡阶段
- 事件处理程序在全局作用域中执行
兼容
attachEvent 和 detachEvent 只能 IE10以及一下版本中使用.
IE事件处理程序与DOM0级的区别
在IE中使用 attachEvent\ detachEvent
, 与 DOM0级不同的地方在于事件处理程序执行时作用域不一样
. 前面讲到过DOM0和 DOM2
事件处理程序都会在所属元素的作用域内运行; 从上面 this.id
打印结果来看, attachEvent
在全局作用域(window)中运行.
到目前为止,把添加事件处理程序方式都一一列举完. 具体引用那种可以根据实际需求选择.
Event 对象常见应用
是伴随着事件执行过程中的一个产物, 这个称之为 event (事件对象). 该事件对象中包含很多跟事件以及用户相关的内容(例如: 事件类型、事件对象、用户点击位置、移动位置等等)
下面罗列下 event object 包含常用属性和方法:
名称 | 描述 | 返回值 |
---|---|---|
type | 指定的事件名称(例如 click, mouseout) | string |
target | 表示要触发事件的元素 | HTMLElement |
currentTarget | 事件处理程序调用时的元素 | HTMLElement |
eventPhase | 事件当前处于事件的那个阶段; 1 捕获 2 事件目标 3 冒泡阶段 | number |
bubbles | 表示当前事件对象是否会向 DOM 树上层元素冒泡 | boolean |
cancelable | 事件的 cancelable 属性表明该事件是否可以被取消默认行为, 如果该事件可以用 preventDefault() 可以阻止与事件关联的默认行为,则返回 true,否则为 false | boolean |
timeStamp | 返回事件发生时的时间戳. | string |
stopPropagation() | 阻止捕获和冒泡阶段中当前事件的进一步传播 如果 bubbles为ture可以使用 | void |
stopImmediatePropagation() | 阻止事件冒泡并且阻止相同事件的其他侦听器被调用 DOM3级中添加 | void |
preventDefault() | 阻止事件默认行为,事件依然会传播除非遇到 stopPropogation 或 stopImmediatePropogation 如果 cancelable为turu则可以使用这个方法 | void |
defaultPrevented | 返回一个布尔值,表明当前事件是否调用了 event.preventDefault()方法 DOM3级中添加 | boolean |
如何来获取事件对象
回顾前面讲解添加事件处理程序(HTML事件处理程序、DOM0 、 DOM2) 分别看怎么来获取
HTML事件处理程序时:
<input type="button" value="ClickMe" onclick="alert(event)" />
<!--or-->
<input type="button" value="ClickMe" onclick="handleClick(event)" />
<script>
function handleClick(event){
console.log(event);
}
</script>
可以通过这两种方式来获取事件对象
DOM0 和 DOM2
<input type="button" value="ClickMe" />
<script>
var target = document.querySelector("input");
target.onclick = function (event){
console.log(event);
};
target.addEventListener("click", function(event){
console.log(event);
});
</script>
可以通过参数的方式来获取, 这是推荐的获取方式. 除了上述几种方式外, 其实可以通过 window.event
来获取表示当前正触发的事件对象, 不过这种方式不是标准(IE), 所以不推荐使用.
event 属性和方法使用
target 和 currentTarget
引用前面的示例, 讲述下 target
和 currentTarget
<div id="container">
<input type="button" value="ClickMe" />
</div>
<script>
var target = document.querySelector("input");
target.addEventListener("click", function(event){
console.log(event.target === target);
console.log(event.currentTarget === target);
});
</script>
控制台输出:
true
true
结合前面对属性描述, event.target
表示当前事件触发的对象. event.currentTarget
表示当前事件处理程序执行时的对象(上下文). 从打印结果来看,当事件处理程序
直接绑定
在目标元素
上时,这两者相同
的.
什么时候不一样咧?
<div id="container">
<input type="button" value="ClickMe" />
</div>
<script>
var target = document.querySelector("input");
var container = document.querySelector("#container");
container.addEventListener("click", function(event){
console.log(event.target === target);
console.log(event.currentTarget === target);
});
</script>
true
false
符合预期, 上面代码中把事件处理程序绑定在了 div#container
元素上, 这时 currentTarget
是当前的事件处理程序绑定的对象.
type
type 用于获取当前事件类型之外, 可以作如下用途, 当希望在同一个事件处理程序中处理多个不同事件类型时, 如下:
<div id="container">
<input type="button" value="ClickMe" />
</div>
<script>
var target = document.querySelector("input");
function handleEntry(event){
if(event.type === "click"){
console.log("click");
} else if(event.type === "dblclick"){
console.log("dblclick");
} else if(event.type === "mouseover"){
console.log("mouseover");
}
}
target.addEventListener("click", handleEntry);
target.addEventListener("dblclick", handleEntry);
target.addEventListener("mouseover", handleEntry);
</script>
cancelable、preventDefault() 、 defaultPrevented
具体上个介绍可以参照前面的表格里面描述, 为什么把这三个结合在一块, 彼此间是有关联的.
先来看看具体示例:
<div id="container">
<input type="button" value="ClickMe" />
</div>
<script>
var target = document.querySelector("input");
function handleOne(event) {
console.log(event.cancelable);
if(event.cancelable){
event.preventDefault()
}
}
function handleTwo(event) {
console.log(event.defaultPrevented);
}
target.addEventListener("click", handleOne);
target.addEventListener("click", handleTwo);
</script>
控制台输出:
true // 表示可以被阻止
true // 表示调用 preventDefault()
什么情况下会有 event.cancelable
返回 false
? 现有 focus、blur、
focusin、focusout 等不允许阻止, 或一些自定义的事件
<div id="container">
<input type="button" value="ClickMe" />
</div>
<script>
var target = document.querySelector("input");
function handleOne(event) {
console.log(event.cancelable);
if(event.cancelable){
event.preventDefault()
}
}
function handleTwo(event) {
console.log(event.defaultPrevented);
}
target.addEventListener("focus", handleOne);
target.addEventListener("focus", handleTwo);
</script>
控制台输出:
false // 表示可以被阻止
false // 表示调用 preventDefault()
bubbles、stopPropagation()、stopImmediatePropagation()
具体上个介绍可以参照前面的表格里面描述, 为什么把这三个结合在一块, 彼此间是有关联的.
先来看看具体示例:
<div id="container">
<input type="button" value="ClickMe" />
</div>
<script>
var target = document.querySelector("input");
function handleOne(event) {
console.log("handleOne");
console.log(event.bubbles);
if(event.bubbles){
event.stopPropagation();
}
}
function handleTwo(event) {
console.log("handleTwo");
}
target.addEventListener("click", handleOne);
target.addEventListener("click", handleTwo);
window.addEventListener("click", function(){
console.log("被阻止拉~");
});
</script>
控制台输出:
"handleOne"
true
"handleTwo"
从输出结果来看, event.stopPropagation()
只会阻止事件冒泡, 并不会阻止同类型多个事件处理程序的执行.
再来看看 stopImmediatePropagation()
修改如下:
<div id="container">
<input type="button" value="ClickMe" />
</div>
<script>
var target = document.querySelector("input");
function handleOne(event) {
console.log("handleOne");
console.log(event.bubbles);
if(event.bubbles){
event.stopImmediatePropagation();
}
}
function handleTwo(event) {
console.log("handleTwo");
}
target.addEventListener("click", handleOne);
target.addEventListener("click", handleTwo);
window.addEventListener("click", function(){
console.log("被阻止拉~");
});
</script>
控制台输出:
"handleOne"
true
event.stopImmediatePropagation()
阻止事件冒泡的同时当前事件类型下其它事件处理程序的执行. 如果 handleOne
中调用 stopImmediatePropagation
, 那么 handleTwo
就不会再执行.
eventPhase
当前事件处于那个阶段
// PhaseType
CAPTURING_PHASE = 1; // 捕获
AT_TARGET = 2; // 事件目标
BUBBLING_PHASE = 3; // 冒泡阶段
先来看看简单示例:
<div id="container">
<input type="button" value="ClickMe" />
</div>
<script>
var target = document.querySelector("input");
function handleOne(event) {
console.log(event.eventPhase);
}
window.addEventListener("click", function(){
console.log(event.eventPhase);
}, true);
target.addEventListener("click", handleOne);
window.addEventListener("click", function(){
console.log(event.eventPhase);
});
</script>
前面讲解都是DOM标准
,也就是所有浏览器厂商都支持的, 那么来看看 IE9 以下版本中会不会有什么不一样.
IE中事件对象
我们也从添加事件处理程序的维度出发, 看看有什么不一样.
HTML事件处理程序
<input type="button" value="ClickMe" onclick="alert(event.type)"/>
正常弹出 click
, 这与前面说到的一样.
DOM0
以前面的示例来演示:
<input type="button" value="ClickMe" id="button"/>
<script>
var target = document.getElementById("button");
target.onclick = function(e){
console.log(e);
console.log(window.event);
}
</script>
控制条输出:
undefined
[object Object]
测试环境: IE8以及一下的版本
在 DOM0 级事件处理程序中, 事件对象添加在 window
上的属性 event
attachEvent
引用上面代码, 使用 attachEvent 来添加事件,并获取 event
<input type="button" value="ClickMe" id="button"/>
var target = document.getElementById("button");
target.attachEvent("onclick", function(e){
console.log(e);
})
事件对象相关的属性
名称 | 描述 | 返回值 |
---|---|---|
type | 指定的事件名称(如 click, mouseout) | string |
srcElement | 表示要触发事件的元素(与DOM中 target) | HTMLElement |
returnValue | 默认值为 true, 但将其设置为 false, 就可以取消事件的默认行为(与DOM中 preventDefault()) | Boolean |
cancelBubble | 默认值为 false, 但将其设置为 ture. 就取消冒泡行为(与DOM中的 stopPropagation()) | Boolean |
看看具体示例:
<div id="container">
<input type="button" value="ClickMe" id="button"/>
<a id="link" href="http://www.baidu.com">百度</a>
</div>
<script>
var container = document.getElementById("container");
var target = document.getElementById("button");
var link = document.getElementById("link");
container.attachEvent("onclick", function(e){
console.log("container");
});
target.attachEvent("onclick", function(e){
console.log(e.srcElement === target);
e.cancelBubble = true; // 阻止冒泡
});
link.attachEvent("onclick", function(e){
e.returnValue = false; // 阻止默认行为(对于超链接来说默认动作就是根据 href进行跳转)
});
</script>
当点击按钮时, 输出结果:
"true"
当点击超链接时, 输出结果:
"container"
从点击超链接来看, e.returnValue
只会阻止默认行为, 事件对象依然进行会冒泡.
事件、目标、事件冒泡、事件默认行为获取方式-汇总
添加事件程序方式 | 语法 | 兼容 |
---|---|---|
HTML事件处理程序 | < element onclick="alert(event)" > | All |
DOM0 | element.onclick = function(event){} | All |
DOM2 | addEventListener | IE9+,Chrome,Firfox,Safari,360 |
IE | attachEvent | IE11< |
获取事件对象 | 方式 | 例子 |
---|---|---|
HTML事件处理程序 | event 变量 | < element onclick="alert(event)" > |
DOM0 | 事件处理程序参数 、IE9以下通过 windiw.event | function(event){}、window.event |
DOM2 | 事件处理程序参数 | function(event){} |
IE | 事件处理程序参数 | function(event){} |
获取目标对象方式 | 属性名 | 兼容 |
---|---|---|
标准 | target | IE9+,Chrome,Firfox,Safari,360 |
IE版本 | srcElement | IE9< |
阻止事件冒泡 | 属性或方法 | 兼容 |
---|---|---|
标准 | stopPropagation()、stopImmediatePropagation() | IE9+,Chrome,Firfox,Safari,360 |
IE版本 | cancelBubble | IE9< |
阻止默认行为 | 属性或方法 | 兼容 |
---|---|---|
标准 | preventDefault() | IE9+,Chrome,Firfox,Safari,360 |
IE版本 | returnValue | IE9< |
事件的操作类(兼容所有版本)
var EventUtil = {
addHandler: function(element, type, handler) {
if(element.addEventListener) {
element.addEventListener(type, handler);
}else if(element.attachEvent){
element.attachEvent("on" + type, handler);
}else {
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler){
if(element.addEventListener) {
element.removeEventListener(type, handler);
}else if(element.attachEvent){
element.detachEvent("on" + type, handler);
}else {
element["on" + type] = null;
}
},
getEvent: function(event){
return event? event : window.event;
},
getTarget: function(event){
return event.target ? event.target:event.srcElement;
},
stopPropagation: function(event){
if(event.stopPropagation) {
event.stopPropagation();
}else {
event.cancelBubble = true;
}
},
preventDefault: function(event){
if(event.preventDefault) {
event.preventDefault();
}else {
event.returnValue = false;
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。