本文是 重温基础 系列文章的第二十篇。html
这是第三个基础系列的第一篇,欢迎持续关注呀!
重温基础 系列的【初级】和【中级】的文章,已经统一整理到个人【Cute-JavaScript】的JavaScript基础系列中。 前端
今日感觉:电影有时候看的是缘分。 git
系列目录:github
本章节复习的是JS中的事件,事件冒泡捕获代理模拟等等。 segmentfault
前置知识:
JavaScript与HTML的交互式经过事件来实现的,是文档或浏览器窗口中发生的一些特定的交互瞬间。浏览器
事件流描述的是从页面中接收事件的顺序,一般有这样两种彻底相反的事件流概念:事件冒泡流(IE团队提出)和事件捕获流(网景团队提出)。微信
冒泡事件(Event Bubbling):事件开始时由最具体的元素接收(文档中嵌套层次最深的那个节点),而后逐层向上传播到较为不具体的节点(文档),看下示例代码:闭包
<!DOCTYPE html> <html> <head> <title>leo 事件冒泡</title> </head> <body> <div id="leo">点击</div> </body> </html>
点击页面中<div>
元素,这个click
事件就会按照下面顺序传播:dom
<div>
<body>
<html>
document
因而可知,元素绑定的事件会经过DOM树向上传播,每层节点都会发生,直到document对象
,如图展现了冒泡过程: 函数
事件捕获(Event Capturing):让不太具体的节点更早接收事件,而最具体的节点最后接收事件,即在事件到达预约目标以前捕获到,看下示例代码(HTML代码和前面同样),事件捕获的过程是这样的:
document
<html>
<body>
<div>
看得出,document对象
最新接收事件,而后沿DOM树依次向下,直到最后的实际目标<div>
元素,如图展现了捕获过程:
注意:因为老版本的浏览器不支持,所以不多人使用事件捕获,不过若是特殊需求仍是可使用事件捕获,建议仍是使用事件冒泡。
“DOM2级事件”规定的事件流包含三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段。
事件捕获为截获事件提供机会,而后实际的目标接收到事件,最后事件冒泡,对事件做出响应。按照前面的HTML代码,整个流程是这样的:
在DOM事件流中,实际目标(<div>
元素)在捕获阶段不接收事件,即在捕获阶段,事件从document对象
到<html>
再到<body>
后就中止,进入“处于目标”阶段,事件在<div>
元素上发生,而后才进入冒泡阶段,将事件传回给文档。
注意:目前主流浏览器都支持DOM事件流,只有IE8和以前版本不支持。
事件处理,即响应某个事件。咱们把事件处理的函数,称为“事件处理程序”。
事件处理程序的名称通常都以on
开头,如click
事件的事件处理程序就是onclick
,load
事件的事件处理程序就是onload
。
咱们将事件处理程序,分为这么几类:
某个元素支持的事件,均可以用一个与相应事件处理程序同名的HTML特性来指定,这个特性的值应该是可以执行的JavaScript代码。好比:
<input type="button" value="点击" onclick="alert('hello leo');">
也能够把须要执行的具体事件单独定义出来,能够放置与单独.js
文件,也能够在文档内用<script>
标签引入:
function fun(){ alert('hello leo'); }
<input type="button" value="点击" onclick="fun()">
咱们经过这样指定事件处理程序,能够有一个局部变量event
来获取事件对象自己,在这个函数内部,this
值等于这个变量event
。
<input type="button" value="点击" onclick="fun(event)">
另外,HTML中指定事件处理程序,会有2个缺点:
可能出现这样的状况:HTML元素触发事件,可是事件处理程序还未定义(函数的定义在HTML最底下定义),就会出现报错,这与HTML代码加载顺序有关。
因为不一样浏览器JavaScript引擎遵循的标识符解析规则存在差别,致使访问非限定对象成员时出错,表现为事件处理程序的做用域链在不一样浏览器结果不一样。
这经常就是不少开发人员放弃HTML事件处理程序的缘由。
经过赋值形式,将一个函数赋值给一个事件处理程序属性。每一个元素(包含window
和document
)都有本身的事件处理属性,这些属性一般所有小写,如onclick
,将这种属性的值设置成一个函数,就能够指定事件处理程序:
var leo = document.getElementById('leo'); leo.onclick = function(){ alert('hello leo!'); }
使用DOM0级方法指定事件处理程序,被认为是元素的方法。此时的事件处理程序是在元素的做用域执行,那么,this就引用当前元素,能够访问元素的任何属性和方法:
var leo = document.getElementById('leo'); leo.onclick = function(){ alert(this.id); // "leo" }
咱们也能够经过设置事件处理程序属性来删除DOM0级的事件处理程序。
leo.onclick = null;
有2个方法:
addEventListener()
removeEventListener()
全部的DOM节点都包含这两个方法,而且它们都接收三个参数:
var leo = document.getElementById('leo'); leo.addEventListener('click',function(){ alert(this.id); // "leo" },false);
与DOM0级方法同样,这里的事件处理程序也会是在元素的做用域中执行,所以this也是指向元素,能够访问元素的任何属性和方法。
使用DOM2级方法,能够添加多个事件处理程序,并按照添加顺序触发:
var leo = document.getElementById('leo'); leo.addEventListener('click',function(){ alert(this.id); // "leo" },false); leo.addEventListener('click',function(){ alert('hello leo!'); // "hello leo!" },false);
注意:经过addEventListener()
添加的事件只能经过removeEventListener()
移除,而且二者传入的参数一致,这就意味着经过addEventListener()
添加的匿名函数不能被移除,看下面代码:
var leo = document.getElementById('leo'); leo.addEventListener('click',function(){ alert(this.id); // "leo" },false); // 没有效果 leo.removeEventListener('click',function(){ // do some thing },false);
没有效果是由于这两个方法传入的函数,是彻底不一样的,为了达到删除事件处理程序的效果,咱们能够将处理函数单独定义出来:
var leo = document.getElementById('leo'); var fun = function(){ alert(this.id); } leo.addEventListener('click',fun,false); // 有效果 leo.removeEventListener('click',fun,false);
IE实现两种方法: attachEvent()
和detachEvent()
。这两个方法接收相同的两个参数:事件处理程序名称和事件处理程序函数。
因为IE8和更早版本只支持事件冒泡,因此经过attachEvent()
添加的事件处理程序会被添加到冒泡阶段。
var leo = document.getElementById('leo'); leo.attachEvent('onclick',function(){ alert('hello leo'); // "leo" },false); // attachEvent也支持添加多个事件处理程序 leo.attachEvent('onclick',function(){ alert('hello world'); // "leo" },false);
注意:这里的第一个参数是onclick
而不是DOM的addEventListener()
的click
。
IE的attachEvent()和DOM0级方法区别:
二者事件处理程序的做用域不一样。
this
指向window
。和DOM0级方法同样,detachEvent()
只能移除使用attachEvent()
添加的方法,为了不没法移除,也是须要将处理的函数单独定义出来:
var leo = document.getElementById('leo'); var fun = function(){ alert(this.id); } leo.attachEvent('onclick',fun,false); // 有效果 leo.detachEvent('onclick',fun,false);
在作跨浏览器事件处理程序,咱们能够有两种方式:
咱们单独受写一个处理方法也不难,只需关注好事件冒泡阶段,咱们能够建立一个方法,区分使用DOM0级方法、DOM2级方法或IE方法便可,默认采用DOM0级方法。
var my_event = { addMyEvent: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; } }; removeMyEvent: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; } } }
当触发一个DOM上的事件时,都会产生一个事件对象event
,并做为参数传入事件处理程序,这个对象包含全部与事件相关的信息。包括致使事件的元素、事件类型等其余信息。
不管指定事件处理程序时使用什么方法(DOM0级方法或DOM2级方法),都会传入event
对象:
var leo = document.getElementById('leo'); leo.onclick = function(event){ alert(event.type); // 'click' } leo.addEventListener('click',function(event){ alert(event.type); // 'click' },false);
event
对象包含与建立它的特定事件相关的属性和方法,经常有以下成员:
咱们可使用event
中的对象和方法:
var leo = document.getElementById('leo'); leo.onclick = function(event){ // 只有当 event 中的 cancelable 属性为true的事件 event.preventDefault(); }
经过调用event.stopPropagation()
方法避免弹框出现两次。
var leo = document.getElementById('leo'); leo.onclick = function(event){ alert('leo'); event.stopPropagation(); } document.body.onclick = function(event){ alert('hello leo'); }
访问IE中的事件对象event
,方法有多种,取决于事件处理程序的方法:
window.event
var leo = document.getElementById('leo'); leo.onclick = function(){ var event = window.event; alert(event.type); // 'click' }
attachEvent
方法,event
做为参数传入(也可使用window.event
)var leo = document.getElementById('leo'); leo.attachEvent('onclick',function(event){ alert(event.type); // 'click' },false);
虽然DOM和IE中event
对象不一样,但咱们也能够和前面的 跨浏览器事件处理程序 处理同样,经过他们之间的区别,实现映射:
var my_event = { myAddFun : function(element, type, handler){ // do some thing }, // 返回对event的引用 getMyEvent : function(event){ return event?event:window.event; }, // 返回事件的目标 getMyTarget : function(event){ return event.target || event.srcElement; }, // 取消事件的默认行为 preventDefault : function(event){ if(event.preventDefault){ event.preventDefault(); }else { event.returnValue = false; } }, myRemoveFun : function(element, type, handler){ // do some thing }, // 阻止事件流 stopPropagetion : function(event){ if(event.stopPropagetion){ event.stopPropagetion(); }else { event.cancelBubble = true; } } } var leo = document.getElementById('leo'); leo.onclick = function(event){ alert('leo'); event = my_event(event); my_event.stopPropagation(event); } leo.onclick = function(event){ alert('hello world'); }
Web浏览器中的事件类型有不少,DOM3级事件规定有如下几类事件类型:
具体每一个方法的详细介绍,能够查看W3school HTML DOM Event 对象
或者查看《JavaScript高级程序设计(第三版)》的第362页开始。
我后面会单独整理一篇,介绍这些事件的文章。
简单理解就是讲一个响应事件委托到另外一个元素。
事件委托利用事件冒泡,指定一个事件处理程序来管理某一类型的全部事件,好比咱们经过一个函数来代替其余三个函数:
<ul id="btn"> <li id="btn1">按钮1</li> <li id="btn2">按钮2</li> <li id="btn3">按钮3</li> </ul>
var btn1 = document.getElementById('btn1'); var btn2 = document.getElementById('btn2'); var btn3 = document.getElementById('btn3'); // my_event 在前面定义了 my_event.myAddFun(btn1, 'click', function(event){ alert('btn1点击'); }); my_event.myAddFun(btn2, 'click', function(event){ alert('btn2点击'); }); my_event.myAddFun(btn3, 'click', function(event){ alert('btn3点击'); });
下面咱们在DOM树层级更高的元素上添加一个事件处理函数,来作事件委托,咱们这么重写这段代码:
var btn = document.getElementById('btn'); my_event.myAddFun(btn, 'click', function(event){ event = my_event.getMyEvent(event); var target = my_event.getMyTarget(event); switch(target.id){ case "btn1": alert('btn1点击'); break; case "btn2": alert('btn2点击'); break; case "btn3": alert('btn3点击'); break; } });
最适合采用事件委托技术的事件包括:click
/mousedown
/mouseup
/keyup
/keydown
/keypress
,虽然mouseover
和mouseout
事件也有冒泡,但由于很差处理它们而且常常须要从新计算元素位置。
能够看出,事件委托有如下优势:
JavaScript的事件模拟主要用来在任意时刻触发特定事件。
在document
对象上使用createEvent()
方法建立一个event
对象。 createEvent()
接收一个参数,即要建立的事件类型的字符串。
DOM2级中,全部这些字符串都使用英文复数形式,DOM3级中都变成单数,也能够是下面中的字符串:
UIEvent
)MouseEvent
)MutationEvent
)HTMLEvent
)建立event
以后,咱们须要使用dispatchEvent()
方法去触发这个事件,须要传入一个参数,参数是须要触发事件的event对象。
全部支持事件的DOM节点都支持这个方法。事件触发以后,事件就能照样冒泡并引起响应事件处理程序的执行。
使用createEvent()
方法传入MouseEvents
建立一个鼠标事件,返回的对象有一个initMouseEvent()
方法,用于指定与该鼠标事件相关的信息,有15个参数:
click
document.defaultView
mouseover
和mouseout
时使用案例:
var btn = document.getElementById('btn'); var myEvent = document.createEvent('MouseEvents'); myEvent.initMouseEvent( 'click', true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null ) btn.dispatchEvent(myEvent);
DOM3级规定,使用createEvent()
方法传入KeyboardEvent
建立一个键盘事件,返回的对象有一个initKeyEvent()
方法,有8个参数:
keydown
document.defaultView
因为DOM3级不提倡使用keypress
事件,所以只能用这个方式来模拟keyup
/keydown
事件。
模拟按住Shift和A键的案例:
var btn = document.getElementById('btn'); var myEvent; // 以DOM3级方式建立 if(document.implementation.hasFeature('KeyboardEvents', '3.0')){ myEvent = document.createEvent('KeyboardEvent'); myEvent.initKeyboardEvent( 'keydown', true, true, document.defaultView, 'a', 0, 'Shift', 0 ); } btn.dispatchEvent(myEvent);
如模拟变更事件和HTML事件。
经过createEvent()
传入MutationEvents
参数建立,返回一个initMutationEvent()
方法,这个方法接收参数包括:type
/bubbles
/cancelable
/relatedNode
/preValue
/newValue
/attrName
/attrChange
,下面模拟一个案例:
var btn = document.getElementById('btn'); var myEvent = document.createEvent('MutationEvents'); myEvent.initMutationEvent( 'DOMNodeInserted', true, false, someNode, '', '', '', 0 ); btn.dispatchEvent(myEvent);
经过createEvent()
传入HTMLEvents
参数建立,返回一个initEvent()
方法,下面模拟一个案例:
var btn = document.getElementById('btn'); var myEvent = document.createEvent('HTMLEvents'); myEvent.initEvent('focus', true, false); btn.dispatchEvent(myEvent);
经过createEvent()
传入CustomEvent
参数建立,返回一个initCustomEvent()
方法,有4个参数:
keydown
event
对象的detail
属性中案例:
var btn = document.getElementById('btn'); var myEvent; // my_event在前面定义 2.6 跨浏览器事件处理程序 my_event.addMyEvent(btn, 'myevent', function(event){ alert('btn detail ', event.detail); }); my_event.addMyEvent(document, 'myevent', function(event){ alert('document detail ', event.detail); }); // 以DOM3级方式穿件 if(document.implementation.hasFeature('CustomEvents', '3.0')){ myEvent = document.createEvent('CustomEvent'); myEvent.initCustomEvent( 'myevent', true, false, 'hello leo', ); btn.dispatchEvent(myEvent); }
IE8及以前的版本模拟事件和DOM中模拟思路类似:想建立event对象
再指定信息,最后触发。
区别在于,IE中使用document.createEventObject()
方法建立event对象
,而且不接收参数,返回一个通用event对象
,咱们要作的就是给这个event对象
添加信息,最后在目标上调用fireEvent()
方法,并接受两个参数(事件处理程序的名称和event对象
)。
在调用fireEvent()
方法会自动添加srcElement
和type
属性,咱们须要手动添加其余属性,下面模拟一个click事件:
var btn = document.getElementById('btn'); var myEvent = document.createEventObject(); myEvent.screenX = 100; myEvent.screenY = 0; myEvent.clientX = 100; myEvent.clientY = 0; myEvent.ctrlKey = false; myEvent.altKey = false; myEvent.shiftKey = false; myEvent.button = 0; btn.fireEvent('onclick', event);
本部份内容到这结束
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推荐 | https://github.com/pingan8787... |
JS小册 | js.pingan8787.com |
欢迎关注微信公众号【前端自习课】天天早晨,与您一块儿学习一篇优秀的前端技术博文 .