avaScript 和 HTML 之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器来预定事件,以便事件发生时执行相应的代码。这种模式在传统的软件工程中叫做观察者模式。
页面的哪一部分会拥有某个特定的事件?设想,在一张纸上画一组同心圆,如果把手指放在圆心上,那手指指向的不是一个圆,而是纸上的所有圆。如果你单击了页面上的某个按钮,你也单击了按钮的容器元素,甚至单击了整个页面。
事件流描述的是从页面中接收事件的顺序。但有意思的是,IE 和 Netscape 提出了完全相反的事件流概念。IE 的事件流是事件冒泡,而 Netscape 的事件流是事件捕获。
事件冒泡,即事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点。以下面的 HTML 页面为例:
<!DOCTYPE html> <html> <head> <title>Event Bubbling Example</title> </head> <body> <div id="myDiv">Click Me</div> </body> </html>
如果你单击了页面中的 div 元素,那么这个click 事件会按照如下的顺序传播:
也就是说,click 事件首先在 div 元素上发生,而这个元素就是我们的单击的元素。然后,click 事件沿 DOM 树向上传播,在每一个节点上都会发生,直至传播到 document 对象。下图展示了事件冒泡的过程:
事件冒泡过程
所有的浏览器都支持事件冒泡,但在具体实现上有一些差别。IE5.5 及更早版本中的事件冒泡会跳过 html 元素(从 body 直接跳到 document)。IE9、Firefox、Chrome 和 Safari 则将事件一直冒泡到 window 对象。
事件捕获的思想是不太具体的节点应该更早接收事件,而最具体的节点应该最后接收事件。事件捕获的用意在于在事件到达预定目标之前捕获它。上面的 HTML 中,如果单击 div 元素按照事件捕获的过程就会下列顺序触发 click 事件:
在事件捕获过程中,document 对象首先接收到 click 事件,然后事件沿 DOM 树依次向下,一直传播到事件的实际目标,即 div 元素。下图展示了事件捕获过程:
事件捕获过程
虽然事件捕获是 Netscape 唯一支持的事件流模型,但 IE9、Safari、Chrome、Opera 和 Firefox 目前都支持这种事件流模型,这些浏览器都是从 window对象开始捕获事件的。由于老版本浏览其不支持,因此很少有人使用事件捕获。
DOM2 级事件规定的事件流包含三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是事件的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。还以前面的 HTML 页面为例,单击 div 元素会按照下图的顺序触发事件:
DOM 事件流过程
在 DOM 事件流中,实际的目标 div 元素在捕获阶段不会接收事件。这意味着在捕获阶段,事件从 document 到 html 再到 body 后就停止了。下一阶段是处于目标阶段,于是事件在 div 上发生,并在事件处理中被看成冒泡阶段的一部分。然后,冒泡阶段发生,事件又传播回文档。多数支持 DOM 事件流的浏览器都实现流一种特定的行为;即使 DOM2 级事件规范明确要求捕获阶段不会涉及事件目标,但 IE9、Safari、Chrome、Firefox 和 Opera9.5及更高版本都会在捕获阶段触发事件对象上的事件,结果,就有两个机会在目标对象上面操作事件。
JavaScript 与 HTML 之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预定事件,以便事件发生时执行相应的代码。这种在传统软件工程中被称为观察员模式的模型,支持页面的行为(JavaScript 代码)与页面的外观(HTML 和 CSS 代码)之间的松耦合。
事件流 描述的是从页面中接收事件的顺序。IE 和 Netscape 开发团队提出了两种实现事件流的方案,事件冒泡流和事件捕获流。
IE 提出的方案为事件冒泡流,从最具体的元素开始触发,然后向上传播至文档节点。
Netscape 团队提出的方案为事件捕获流,事件捕获由文档节点开始传播至最具体的元素。事件捕获实际上是为在事件到达最终目标前拦截事件。
DOM2 Events 规范规定事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。
在 DOM 事件流中,实际的目标(<div>元素)在捕获阶段不会收到事件。这是因为捕获阶段从document到<html>再到<body>就结束了。下一阶段,即会在<div>元素上触发事件的"到达目标"阶段,通常在事件处理时被认为是冒泡阶段的一部分。然后,冒泡阶段开始,事件反向传播至文档。
事件就是在浏览器上执行的某种动作。如click、load等。事件处理程序(或事件监听器)就是响应事件而调用的函数。
可以在支持事件的HTML元素上通过属性来指定能够执行的JavaScript代码的值。如下所示:
<input type="button" value="HTML Event Handler" onclick="alert('Click HTML Event')" />
在 HTML 中定义的事件处理程序可以包含精确的动作指令,也可以调用在页面其他地方定义的脚本:
<input type="button" value="HTML Event Handler" onclick="handleHTMLEvent()" />
<script>
function handleHTMLEvent() {
alert("Click HTML Event");
}
</script>
在调用handleHTMLEvent()函数之前点击了按钮,会发生错误。可以将HTML事件处理程序封装在try/catch块中:
<input type="button" value="HTML Event Handler 2" onclick="try{handleHTMLEvent()} catch (e) {}" />
这种事件处理程序会使HTML与JavaScript强耦合,不利于维护。
通过JavaScript获取事件处理程序并将一个函数赋值给该事件处理程序属性。
<input id="btn" type="button" value="DOM Event Handler" />
<script>
var btn=document.getElementById('btn');
btn.onclick=function () {
alert("Click DOM Event");
}
</script>
使用DOM0方式为事件处理程序赋值的函数被认为是元素的方法。此时获取的this等于事件处理程序所在的元素。以这种方式添加事件处理程序注册在事件流的冒泡阶段。
DOM2 Events 定义addEventListener() 和 removeEventListener() 两个方法,用于事件处理程序的赋值和移除。所有DOM节点都含这两个方法,它们接收 3 个参数:事件名、事件处理函数和一个布尔值,布尔值表示调用事件处理程序的事件流阶段,true在捕获阶段,false(默认)在冒泡阶段。
<input id="btn" type="button" value="DOM Event Handler" />
<script>
var btn=document.getElementById('btn');
btn.addEventListener("click", ()=> {
alert("DOM2 Event Handler");
}, false);
</script>
使用addEventListener()可以为同一个事件添加多个事件处理程序。
var btn=document.getElementById('btn');
btn.addEventListener("click", ()=> {
alert("DOM2 Event Handler");
}, false);
btn.addEventListener("click", ()=> {
alert("Rep DOM2 Event Handler");
}, false);
使用removeEventListener()并传入与addEventListener()同样的参数来移除。
var btn=document.getElementById('btn');
btn.addEventListener("click", ()=> {
alert("DOM2 Event Handler");
}, false);
btn.removeEventListener("click", function () { // 无效果
alert("Remove DOM2 Event Handler");
}, false);
但这种传入匿名函数的事件处理程序无法移除。removeEventListener()必须和addEventListener()传入的事件处理函数必须是同一个。
var btn=document.getElementById('btn');
var handler=function() {
alert("DOM2 Event Handler");
}
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false); // 有效果
大多数情况下,事件处理程序会被添加到事件流的冒泡阶段,主要原因是跨浏览器兼容性好。把事件处理程序注册到捕获阶段通常用于在事件到达其指定目标之前拦截事件。如果不需要拦截,则不要使用事件捕获。
IE 实现了 attachEvent() 和 detachEvent() 方法用于事件处理程序的赋值和移除。接收两个同样的参数:事件处理程序的名字和事件处理函数。IE8以前只支持事件冒泡,使用attachEvent()会添加到冒泡阶段。
var btn=document.getElementById("btn");
btn.attachEvent("onclick", function() {
alert("IE Event Handler");
});
此时的事件处理程序是在全局作用域中运行。this等于window。也可以给一个元素添加多个事件处理程序,但事件处理程序会以添加的顺序的反向触发。
DOM发生事件时会将相关信息收集并存储在event对象中。该对象会包含一些事件的元素、类型等基本信息。
在浏览器中,event对象是传给事件处理程序的唯一参数。在添加事件处理程序时可以使用事件对象:
<input id="btn" type="button" value="DOM Event Object" onclick="alert(event.type)">
<script>
var btn=document.getElementById("btn");
btn.onclick=function(event) {
alert(event.type);
};
btn.addEventListener("click", (event)=> {
alert(event.type);
}, false);
</script>
下表列出全部属性和方法:
属性/方法 | 类型 | 读/写 | 说明 |
bubbles | 布尔值 | 只读 | 表示事件是否冒泡 |
cancelable | 布尔值 | 只读 | 表示事件是否可以被取消 |
currentTarget | 元素 | 只读 | 当前事件处理程序所在的元素 |
defaultPrevented | 布尔值 | 只读 | true表示已经调用preventDefault()方法(DOM3 Events中新增) |
detail | 整数 | 只读 | 只有UIEvent(用户界面)事件才具有。返回一个数值,表示事件的某种信息。如click事件,detail返回的是鼠标按下的次数。 |
eventPhase | 整数 | 只读 | 表示事件流正被处理到了哪个阶段:1代表捕获阶段,2代表到达目标,3代表冒泡阶段 |
target | 元素 | 只读 | 事件目标 |
isTrusted | 布尔值 | 只读 | true表示事件是由浏览器发起的。false表示事件是由脚本创建、修改、通过EventTarget.dispatchEvent()派发 |
type | 字符串 | 只读 | 被触发的事件类型 |
preventDefault() | 函数 | 只读 | 用于取消事件的默认行为。只有cancelable为true才可以调用这个方法 |
stopImmediatePropagation() | 函数 | 只读 | 用于取消后续事件捕获或事件冒泡,并阻止调用任何后续事件处理程序(DOM3 Events中新增) |
stopPropagation() | 函数 | 只读 | 阻止捕获和冒泡阶段中当前事件的进一步传播 |
事件处理程序内部,对于currentTarget和target两个属性,将事件处理程序直接添加在目标上时,this和它们是相等的。
<body>
<input id="btn" type="button" value="DOM Event Object" />
</body>
<script>
let btn=document.getElementById("btn");
btn.onclick=function(event) {
alert(event.currentTarget===this);
alert(event.target===this);
};
</script>
如果将事件处理程序添加到按钮的父结点上,结果就是currentTarget和document.body和this相等,因为它是注册事件处理程序的元素。而target属性等于按钮本身,因为click事件才是真正的目标。但按钮本身没注册事件处理程序,click事件冒泡到document.body,触发注册的处理程序。
document.body.onclick=function (event) {
alert(event.currentTarget===document.body); // true
alert(this===document.body); // true
alert(event.target===this); // false
alert(event.target===document.getElementById("btn")); // true
}
preventDefault()方法用于阻止特定事件的默认动作。比如,链接的默认行为就是在被单机时导航到href属性指定的URL。如果想阻止这个导航行为,可以在onclick事件处理程序中取消,如下面的例子所示:
let link=document.getElementById('link');
link.onclick=function (event) {
event.preventDefault();
}
任何可以通过preventDefault()取消默认行为的事件,其事件对象的cancelable属性都会设置为true。
stopPropagation()方法用于立即阻止事件流在DOM结构中传播,取消后续的事件捕获或冒泡。例如,直接添加到按钮的事件处理程序中调用stopPropagation(),可以阻止document.body上注册事件处理程序执行。比如:
let btn=document.getElementById("btn");
btn.onclick=function(event) {
console.log("Clicked");
event.stopPropagation();
};
document.body.onclick=function(event) {
console.log("Body clicked");
};
IE事件对象也包含与导致其创建的特定事件相关的属性和方法,很多都与DOM属性和方法对应。如srcElement对应DOM中的target,returnValue属性对应DOM中的preventDefault()方法等等。
DOM3 Events在 DOM2 Events 基础上重新定义了并增加了新的事件类型:
用户界面事件或 UI 事件不一定跟用户操作有关。这类事件在 DOM 规范出现之前就已经以某种形式存在了,保留它们是为了向后兼容。UI 事件主要有以下几种。
大多数HTML事件与window对象和表单控件有关。
除了DOMActivate,这些事件在DOM2 Events中都被归为HTML Events。
当页面中的某元素获得(用户选中)或失去焦点时触发焦点事件。焦点事件有以下几种:
DOMFocusIn 和 DOMFocusOut 是 focus 与 blur 的冒泡版,这两个事件是Opera新增的,但已被弃用,可能还有一些浏览器在支持。focusin 和 focusout,这两个事件是IE浏览器新增的,已被DOM3 Events标准化。
焦点从页面中一个元素转移到另一个元素时,会依次发生如下事件:①focusout在失去焦点的元素上触发;②focusin在获得焦点的元素上触发;③blur在失去焦点的元素上触发;④DOMFocusOut在失去焦点的元素上触发;⑤focus在获得焦点的元素上触发;⑥DOMFocusIn在获得焦点的元素上触发。
鼠标事件是较常用的事件类型,继承 MouseEvent 接口。DOM Events 定义了 9 中鼠标事件。
事件之间存在关系,取消鼠标事件的默认行为会影响其它事件。
键盘事件由用户操作键盘时触发。DOM2 Events最终没有定义键盘事件,都是基于DOM0实现的。
DOM3 Events 为键盘事件提供了规范,包含 3 个事件:
虽然所有元素都支持这些事件,但当用户在文本框中输入内容时最容易看到。
用户按下键盘上的某个字符键触发事件的正确顺序如下:①keydown;②keypress;③keyup。keydown和keypress事件会在文本框出现变化之前触发,keyup事件在文本框出现变化之后触发。非字符键会省略keypress事件。
但当用户在按下键足够长的时间时,它会开始“自动重复事件”:keydown和keypress不断被重复,直到按键释放时,才会触发keyup事件。非字符键会省略keypress事件步骤,不断触发keydown事件,直到释放才会触发keyup事件。
DOM3 Events 引入 textInput 输入事件,是对 keypress 事件的扩展,用于在文本显示给用户之前更方便地截获文本输入。textInput会在文本被插入到文本框之前触发。
只有可编辑区域才支持 textInput 事件,如文本域,并且只有当插入字符时才触发。该事件为 Event 对象添加了 event.data 属性,该属性包含新插入的字符。
DOM3 Events新增合成事件,用于处理使用IME输入时的复杂输入序列。IME可以让用户输入物理键盘上没有的字符。如使用拉丁字母键盘的用户可以使用IME输入日文。IME通常需要按下多个键才能输入一个字符。合成事件用于检测和控制这种输入。合成事件有以下 3 中:
合成事件在很多方面与输入事件很类似。在合成事件触发时,事件目标是接收文本的输入字段。唯一增加的事件属性是 data,其中包含的值视情况而异:
与文本事件类似,合成事件可以用来在必要时过滤输入内容。可以像下面这样使用合成事件:
let textbox=document.getElementById("text");
textbox.addEventListener("compositionstart", (event)=> {
console.log(event.data);
});
textbox.addEventListener("compositionupdate", (event)=> {
console.log(event.data);
});
textbox.addEventListener("compositionend", (event)=> {
console.log(event.data);
})
除了上述事件类型外,DOM2的变化事件(Mutation Events)被Mutation Observers取代。还有用于定义设备及设备相关的设备事件、用于触摸设备的触摸事件,还有资源事件等等,就不一一列举了。
DOM3增加了自定义事件的类型。自定义事件不会触发原生DOM事件,但可以创建全新的事件类型,使用document.createEvent(type)创建事件类型,type是一个字符串,表示创建的事件类型。如"UIEvents"、"MouseEvents"等。创建自定义事件,就需要传入"CustomEvent"。
创建事件后,需要调用initCustomEvent()对事件进行初始化,该方法接收 4 个参数。
自定义事件可以像其它事件一样在DOM中派发。如:
<div id="div" value="Dom Custom Event Handler">DOM Custom Event Handler</div>
<script>
let div=document.getElementById("div");
div.addEventListener("cusEvent", (event)=> {
console.log("DIV: " + event.detail);
});
document.addEventListener("cusEvent", (event)=> {
console.log("DOCUMENT: " + event.detail);
});
let event=document.createEvent("CustomEvent");
event.initCustomEvent("cusEvent", true, false, "Custom Event");
div.dispatchEvent(event);
</script>
在initCustomEvent第二个参数设置了可以冒泡,因此浏览器会负责把事件冒泡到document。
DOM4添加了对CustomEvent构造函数的支持,可以使用new CustomEvent(type, eventInitDict)来创建自定义事件。它有以下参数。
而 eventInitDict参数的类型是CustomEventInit,它有几个参数。
举一个例子:
<div id="div" value="Dom Custom Event Handler">DOM Custom Event Handler</div>
<script>
let div=document.getElementById("div");
div.addEventListener("cusEvent", (event)=> {
console.log("DIV: " + event.detail);
});
document.addEventListener("cusEvent", (event)=> {
console.log("DOCUMENT: " + event.detail);
});
let event=new CustomEvent("cusEvent", {detail: {name: "大卫"}, bubbles: true});
div.dispatchEvent(event);
</script>
事件委托利用事件冒泡,使用一个事件处理程序来管理同一类型的事件。
<ul id="links">
<li id="red">Red</li>
<li id="green">Green</li>
<li id="blue">Blue</li>
</ul>
通常给<li>元素添加点击事件,通常给三个<li>元素分配三个onclick处理程序。与其如此,我们可以在<ul>元素上添加一个事件处理程序捕获所有的<li>元素的点击事件:
let list=document.getElementById('links');
list.addEventListener("click", (event)=> {
let target=event.target;
switch (target.id) {
case "red":
document.title="大红大紫";
break;
case "green":
location.href="http://www.sample.com";
break;
case "blue":
alert("Hi, Blue");
break;
}
});
这样,点击每个<li>元素,都会将事件向上冒泡,交给父节点<ul>的事件处理程序来处理,在<ul>的事件中,通过event.target的id属性可以确定哪个元素被点击,然后执行相应操作。
事件委托具有如下优点:
可以先将事件处理程序删除,在通过innerHTML属性来对DOM进行删除操作,来删除不用的事件处理程序,来提高Web应用性能。
<div id="div">
<input id="btn" type="button" value="Del Event Handler">
</div>
<script>
let btn=document.getElementById("btn");
btn.onclick=function () {
// 执行操作
...
btn.onclick=null; // 删除事件处理程序
document.getElementById("div").innerHTML="Processing...";
}
</script>
事件是JavaScript与网页交互的主要方式。、本文主要介绍了事件的接收顺序,事件对象和常见的事件类型,还有一些事件类型如HTML5事件、设备事件等没有介绍,还有一些浏览器也会实现自己专有的事件,方便扩展满足用户的需求的功能。事件在JavaScript中非常重要,理解事件的原理对其性能也非常重要。
Javascript的DOM中,关于事件Event对象的知识是一定要掌握的。Event对象模型主要分为两个部分,一个是Event对象本身具有的属性和方法,这个参照API就可以学得;另一个是在DOM节点上绑定的事件,例如click,dblclick,mouseenter等,以及事件在DOM节点中的传播。今天我们主要就javascript中事件流的传播过程以及不同DOM级事件处理程序进行讲解。
事件流
事件流可以理解为事件在页面的DOM节点之间传播的顺序,主要分为三个过程,分别是:事件捕获阶段 --> 事件目标阶段 --> 事件冒泡阶段,从下图可以看出事件的传播过程
DOM的事件流
事件捕获
事件捕获的思想是页面上最外层的节点先接收事件,然后向内层元素逐级传播。例如上面的例子中,事件捕获阶段的传播顺序为:window --> document --> html --> body --> table --> tbody --> tr --> td
事件冒泡
事件冒泡和事件捕获刚好相反,它的思想是让最内层节点先接收事件,然后向外层逐级传播。上面的例子中,事件冒泡阶段传播顺序为:td --> tr --> tbody --> table --> body --> html --> document --> window
事件目标阶段
不管在事件传播阶段还是在事件冒泡阶段,都必然经历事件目标阶段,表示对DOM节点的事件进行处理
事件处理程序
事件处理程序实际上就是绑定在DOM节点上的事件函数。在W3C标准中,分为DOM0,DOM2和DOM3,而在DOM1中没有定义事件相关内容,因此没有DOM1级事件模型。
DOM0级事件模型
通过将函数直接赋值给事件处理属性,在DOM0中只支持事件冒泡的过程
DOM0事件处理程序
DOM2级事件处理程序
在DOM2中,事件处理过程会通过不同的参数配置来决定是采用事件捕获还是事件冒泡,还是两个过程都执行。由于浏览器的不同,DOM2中事件绑定方法也不一样
IE浏览器
在IE10及以下版本的浏览器中,只支持事件冒泡,而在IE11中又添加了对事件捕获的支持。在IE10及以下版本中,通过下列方法添加或删除事件
IE10以下事件处理程序
非IE浏览器
非IE的事件处理程序
通过useCapture参数来确定采用哪种方式,true表示事件捕获,false表示事件冒泡,默认状态为false。
IE和非IE浏览器的比较
相同点
(1)支持同一个元素上绑定多个事件处理函数
多个事件处理函数
(2)不能删除匿名函数,删除时必须和绑定的函数一致
不能删除匿名函数
不同点
(1)在使用attachEvent方法为同一个事件添加多个函数时,执行顺序与绑定的顺序相反
attachEvent绑定事件
(2)attachEvent执行的作用域指向全局环境,因此this指向window;而addEventListener执行的作用域在DOM元素内部,因此this指向绑定的元素
兼容性处理
兼容性处理
DOM3级事件处理程序
DOM3在保留了DOM2特性的基础上,添加了一些新方法。最大的改进就是DOM3允许自定义事件,通过createEvent()方法完成,返回的方法通过initCustomEvent()方法来初始化。
DOM3自定义事件
上述例子表示,创建一个冒泡事件'myEvent',然后绑定在div和document上,因为采用事件冒泡,因此会先alert出‘div myEvent’,再输出‘document myEvent’。
总结
以上是一些关于Event的事件流和不同DOM级事件处理程序知识的介绍,希望对大家有用。
如果喜欢的话,记得关注小编噢,小编后续会坚持出更多技术性的文章,如果有任何问题,也欢迎提问,小编都会尽力解答的。
*请认真填写需求信息,我们会在24小时内与您取得联系。