本文是 重温基础 系列文章的第二十篇。这是第三个基础系列的第一篇,欢迎持续关注呀! 重温基础 系列的【初级】和【中级】的文章,已经统一整理到我的【Cute-JavaScript】的JavaScript基础系列中。 今日感受:电影有时候看的是缘分。 系列目录:【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理)【重温基础】1-14篇【重温基础】15.JS对象介绍【重温基础】16.JSON对象介绍【重温基础】18.相等性判断【重温基础】19.闭包本章节复习的是JS中的事件,事件冒泡捕获代理模拟等等。 前置知识: JavaScript与HTML的交互式通过事件来实现的,是文档或浏览器窗口中发生的一些特定的交互瞬间。1.事件流事件流描述的是从页面中接收事件的顺序,通常有这样两种完全相反的事件流概念:事件冒泡流(IE团队提出)和事件捕获流(网景团队提出)。1.1 事件冒泡冒泡事件(Event Bubbling):事件开始时由最具体的元素接收(文档中嵌套层次最深的那个节点),然后逐层向上传播到较为不具体的节点(文档),看下示例代码:<!DOCTYPE html><html><head> <title>leo 事件冒泡</title></head><body> <div id=“leo”>点击</div></body></html>点击页面中<div>元素,这个click事件就会按照下面顺序传播:<div> <body><html> document由此可见,元素绑定的事件会通过DOM树向上传播,每层节点都会发生,直到document对象,如图展示了冒泡过程: 1.2 事件捕获事件捕获(Event Capturing):让不太具体的节点更早接收事件,而最具体的节点最后接收事件,即在事件到达预定目标之前捕获到,看下示例代码(HTML代码和前面一样),事件捕获的过程是这样的:document<html> <body><div> 看得出,document对象最新接收事件,然后沿DOM树依次向下,直到最后的实际目标<div>元素,如图展示了捕获过程: 注意:由于老版本的浏览器不支持,因此很少人使用事件捕获,不过如果特殊需求还是可以使用事件捕获,建议还是使用事件冒泡。1.3 DOM事件流“DOM2级事件”规定的事件流包含三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段。 事件捕获为截获事件提供机会,然后实际的目标接收到事件,最后事件冒泡,对事件作出响应。按照前面的HTML代码,整个流程是这样的: 在DOM事件流中,实际目标(<div>元素)在捕获阶段不接收事件,即在捕获阶段,事件从document对象到<html>再到<body>后就停止,进入“处于目标”阶段,事件在<div>元素上发生,然后才进入冒泡阶段,将事件传回给文档。 注意:目前主流浏览器都支持DOM事件流,只有IE8和之前版本不支持。2.事件处理事件处理,即响应某个事件。我们把事件处理的函数,称为“事件处理程序”。 事件处理程序的名称一般都以on开头,如click事件的事件处理程序就是onclick,load事件的事件处理程序就是onload。 我们将事件处理程序,分为这么几类:HTML事件处理程序DOM0级事件处理程序DOM2级事件处理程序IE事件处理程序跨浏览器事件处理程序2.1 HTML事件处理程序某个元素支持的事件,都可以用一个与相应事件处理程序同名的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和JavaScript代码紧密耦合这常常就是很多开发人员放弃HTML事件处理程序的原因。2.2 DOM0级事件处理程序通过赋值形式,将一个函数赋值给一个事件处理程序属性。每个元素(包含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.3 DOM2级事件处理程序有2个方法:添加事件处理程序:addEventListener()删除事件处理程序:removeEventListener()所有的DOM节点都包含这两个方法,并且它们都接收三个参数:处理的事件名称事件处理程序的函数布尔值(true:事件捕获阶段调用,false:事件冒泡阶段调用)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);2.4 IE事件处理程序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级方法区别: 两者事件处理程序的作用域不同。DOM0级方法,作用域在所属元素的作用域。attachEvent(),作用域在全局作用域,即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);2.6 跨浏览器事件处理程序在做跨浏览器事件处理程序,我们可以有两种方式:使用能够隔离浏览器差异的JavaScript库单独手写一个处理方法我们单独受写一个处理方法也不难,只需关注好事件冒泡阶段,我们可以创建一个方法,区分使用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; } }}3.事件对象当触发一个DOM上的事件时,都会产生一个事件对象event,并作为参数传入事件处理程序,这个对象包含所有与事件相关的信息。包括导致事件的元素、事件类型等其他信息。3.1 DOM中的事件对象无论指定事件处理程序时使用什么方法(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();}立即停止事件在DOM的传播通过调用event.stopPropagation()方法避免弹框出现两次。var leo = document.getElementById(’leo’);leo.onclick = function(event){ alert(’leo’); event.stopPropagation();}document.body.onclick = function(event){ alert(‘hello leo’);}3.2 IE中的事件对象访问IE中的事件对象event,方法有多种,取决于事件处理程序的方法:DOM0级方法,使用window.eventvar leo = document.getElementById(’leo’);leo.onclick = function(){ var event = window.event; alert(event.type); // ‘click’}IE的attachEvent方法,event作为参数传入(也可以使用window.event)var leo = document.getElementById(’leo’);leo.attachEvent(‘onclick’,function(event){ alert(event.type); // ‘click’},false);3.3 跨浏览器的事件对象虽然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’);}4.事件类型Web浏览器中的事件类型有很多,DOM3级事件规定有以下几类事件类型:UI事件:当用户与页面上元素交互时触发;焦点事件:当元素失去或获取焦点时触发;鼠标事件:当用户通过鼠标在页面操作时触发;滚轮事件:当使用鼠标滚轮(或类似设备)时触发;文本事件:当在文档中输入文本时触发;键盘事件:当用户通过键盘操作时触发;合成事件:当为IME输入字符时触发;变动事件:当底层DOM结构变动时触发;具体每个方法的详细介绍,可以查看W3school HTML DOM Event 对象 或者查看《JavaScript高级程序设计(第三版)》的第362页开始。 我后面会单独整理一篇,介绍这些事件的文章。5.事件委托简单理解就是讲一个响应事件委托到另一个元素。 事件委托利用事件冒泡,指定一个事件处理程序来管理某一类型的所有事件,比如我们通过一个函数来代替其他三个函数:<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事件也有冒泡,但因为不好处理它们并且经常需要重新计算元素位置。 可以看出,事件委托有以下优点:减少内存消耗动态绑定事件6.事件模拟JavaScript的事件模拟主要用来在任意时刻触发特定事件。6.1 DOM中的事件模拟在document对象上使用createEvent()方法创建一个event对象。 createEvent()接收一个参数,即要创建的事件类型的字符串。 DOM2级中,所有这些字符串都使用英文复数形式,DOM3级中都变成单数,也可以是下面中的字符串:UIEvents : 一般化的UI事件(鼠标和键盘事件都继承自UI事件)(DOM3级中UIEvent)MouseEvents : 一般化的鼠标事件(DOM3级中MouseEvent)MutationEvents : 一般化的DOM滚动事件(DOM3级中MutationEvent)HTMLEvents : 一般化的HTML事件(DOM3级中HTMLEvent)创建event之后,我们需要使用dispatchEvent()方法去触发这个事件,需要传入一个参数,参数是需要触发事件的event对象。所有支持事件的DOM节点都支持这个方法。事件触发之后,事件就能照样冒泡并引发响应事件处理程序的执行。6.1.1 模拟鼠标事件使用createEvent()方法传入MouseEvents创建一个鼠标事件,返回的对象有一个initMouseEvent()方法,用于指定与该鼠标事件相关的信息,有15个参数:type : 字符串,表示触发的事件类型,如clickbubble : 布尔值,表示是否冒泡,为了精确模拟鼠标事件,通常设置为truecancelable :布尔值,表示是否可以取消,为了精确模拟鼠标事件,通常设置为trueview : 与事件关联的视图,基本都设置为document.defaultViewdetail : 整数,与事件有关的详细信息,基本设置为0screenX : 整数,事件相对屏幕的X坐标screenY : 整数,事件相对屏幕的Y坐标clientX : 整数,事件相对视口的X坐标clientY : 整数,事件相对视口的Y坐标ctrlKey : 布尔值,表示是否按下Ctrl键,默认falsealtKey : 布尔值,表示是否按下Alt键,默认falseshiftKey : 布尔值,表示是否按下Shift键,默认falsemetaKey : 布尔值,表示是否按下Meta键,默认falsebutton : 整数,表示按下哪个鼠标键,默认0relatedTarget : 对象,表示与事件相关的对象,只在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);6.1.2 模拟键盘事件DOM3级规定,使用createEvent()方法传入KeyboardEvent创建一个键盘事件,返回的对象有一个initKeyEvent()方法,有8个参数:type : 字符串,表示触发的事件类型,如keydownbubble : 布尔值,表示是否冒泡,为了精确模拟键盘事件,通常设置为truecancelable :布尔值,表示是否可以取消,为了精确模拟键盘事件,通常设置为trueview : 与事件关联的视图,基本都设置为document.defaultViewkey : 整数,表示按下的键的键码localtion : 整数,表示按下哪里的键,默认0表示主键盘,1表示左,2表示右,3表示数字键盘,4表示移动设备(即虚拟键盘),5表示手柄modifiers : 字符串,空格分隔的修改件列表,如"shift"repeat : 整数,在一行中按了多少次这个键由于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);6.1.3 模拟其他事件如模拟变动事件和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);模拟HTML事件通过createEvent()传入HTMLEvents参数创建,返回一个initEvent()方法,下面模拟一个案例:var btn = document.getElementById(‘btn’);var myEvent = document.createEvent(‘HTMLEvents’);myEvent.initEvent(‘focus’, true, false);btn.dispatchEvent(myEvent);6.1.4 自定义DOM事件通过createEvent()传入CustomEvent参数创建,返回一个initCustomEvent()方法,有4个参数:type : 字符串,表示触发的事件类型,如keydownbubble : 布尔值,表示是否冒泡,为了精确模拟键盘事件,通常设置为truecancelable :布尔值,表示是否可以取消,为了精确模拟键盘事件,通常设置为truedetail : 对象,任意值,保存在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);}6.2 IE中的事件模拟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);参考文章《JavaScript高级程序设计》第13章 事件本部分内容到这结束Author王平安E-mailpingan8787@qq.com博 客www.pingan8787.com微 信pingan8787每日文章推荐https://github.com/pingan8787…JS小册js.pingan8787.com