Event 事件

最后更新于:2022-04-01 23:53:50

## Event 事件 客户端JavaScript程序采用了异步事件驱动编程模型。 **一、相关术语** `事件流`描述的是从页面中接收事件的顺序。 `事件`就是Web浏览器通知应用程序发生了什么事情。 `事件类型(event type)`是一个用来说明发生什么类型事件的字符串。例如,“mousemove”表示用户移动鼠标,“keydown”表示键盘上某个键被按下等等。 `事件目标(event target)`是发生的事件与之相关的对象。当讲事件时,我必须同时指明类型和目标。比如:window上的load事件或` var v = document.getElementById('mybutton'); v.onclick = function() {alert('1');} v.addEventListener('click',function(){alert('2');},false); ``` 上面的代码中,单击按钮会产生两个alert()对话框。 能通过多次调用addEventListener()方法为同一个对象注册同一事件类型的多个处理程序函数。 所有该事件类型的注册处理程序都会按照注册的顺序调用。使用相同的参数在同一个对象上多次调用addEventListener()是没用的,处理程序仍然只注册一次,同时重复调用也不会改变调用处理程序的顺序(也就是说,如果为同一个事件多次添加同一个监听函数,函数只会执行一次,多余的添加将自动删除)。 相对`addEventListener()`的是 `removeEventListener() `方法。 removeEventListener方法的参数,与addEventListener方法完全一致。它的第一个参数“事件类型”,也是大小写不敏感。 注意:removeEventListener()方法的事件处理程序函数必须是函数名。 **dispatchEvent()** dispatchEvent方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault(),则返回值为false,否则为true。 ``` target.dispatchEvent(event) ``` dispatchEvent方法的参数是一个Event对象的实例。 **在IE上** IE支持事件冒泡流,不支持事件捕获流。 IE9以前的IE不支持`addEventListener()`和`removeEventListener()`。不过我们可以使用类似的方法`attachEvent()`和`detachEvent()`; `attachEvent()`和`detachEvent()`方法的工作原理与addEventListener()和removeEventListener()类似,但有所区别: - 因为IE事件模型不支持事件捕获,所以attachEvent()和detachEvent()只有两个参数:事件类型和处理程序函数 - IE方法的第一个参数使用了带“on”前缀的事件处理程序属性名。例如:当给addEventListener()传递“click”时,要给attachEvent()传递“onclick” - attachEvent()允许相同的事件处理程序函数注册多次。当特定的事件类型发生时,注册函数的调用次数和注册次数一样。 下面的代码中,创建一个EventUtil工具类,可以兼容ie浏览器: ``` var EventUtil = { addHandler: function(element, type, handler, useCapture) { if(element.addEventListener) { element.addEventListener(type, handler, useCapture ? true : false); } else if(element.attachEvent) { element.attachEvent('on' + type, handler); } else if(element != window) { element['on' + type] = handler; } }, removeHandler: function(element, type, handler, useCapture) { if(element.removeEventListener) { element.removeEventListener(type, handler, useCapture ? true : false); } else if(element.detachEvent) { element.detachEvent('on' + type, handler); } else if(element != window) { element['on' + type] = null; } } }; ``` 在上面的EventUtil工具类中,我们创建addHandler()用来绑定事件,removeHandler用来删除事件。 **10.2 事件处理程序的调用** 一旦注册了事件处理程序,浏览器就会在指定对象上发生指定类型事件时自动调用它。 **10.2.1 事件处理程序的参数** 通常调用事件处理程序时把`事件对象(event)`作为它们的一个参数。事件对象的属性提供了有关事件的详细信息。例如,type属性指定了发生的事件类型。 在IE8及以前的版本中,通过设置属性注册事件处理程序,当调用它们时并未传递事件对象。取而代之,需要通过全局对象window.event来获得事件对象。 下面的代码就是考虑了兼容性: ``` function handler(event){ event = event || window.event; } ``` **10.2.2 event事件对象** **(1)DOM中的事件对象** event对象包含与创建它的特定事件有关的属性和方法,触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都有下列属性和方法: - bubbles 布尔值,只读,表示事件是否冒泡 - cancelable 布尔值,只读,表示是否可以取消事件的默认行为 - currentTarget Element类型,只读,其事件处理程序当前正在处理事件的那个元素 - defaultPrevented 布尔值,只读,为true时表示已经调用了preventDefault() - detail Integer类型,只读,与事件相关的细节信息 - eventPhase Integer类型,只读,调用事件处理程序的阶段:1表示捕获阶段,2表示目标阶段,3表示冒泡阶段 - preventDefault() Function类型,只读,取消事件的默认行为 - stopImmediatePropagation() Function类型,只读,取消事件的进一步捕获或冒泡,同时组织任何事件处理程序被调用 - stopPropagation() Function类型,只读,取消事件的进一步捕获或冒泡 - target Element,只读,事件的目标 - trusted 布尔值,只读,为true时表示事件是浏览器生成的,为false时表示事件是开发人员创建的 - type String类型,只读,事件类型 - view AbstractView,只读,与事件关联的抽象视图,等同于发生事件的window对象 在事件处理程序内部,对象this始终等于currentTarget的值,而target则表示实际的目标。 **(2)IE中的事件对象** 对于IE,event对象是绑定在window对象中的: ``` window.event ``` IE的event对象也同样包含与创建它的事件相关的属性和方法,也具有一些共同属性和方法: - cancelBubble 布尔值,可读写,默认值为false,将其设置为true时,作用和DOM中的stoPropagation()方法一样 - returnValue 布尔值,可读写,默认为true,将其设为false时,作用和DOM中的preventDefault()方法的作用一样。 - srcElement Element,只读,事件的目标(与DOM中的target属性相同) - type String,只读,事件类型 基于IE和DOM事件对象不一样,我们可以工具类:EventUtil里添加方法: ``` var EventUtil = { addHandler: function(element, type, handler, useCapture) { // 省略代码 }, removeHandler: function(element, type, handler, useCapture) { // 省略代码 }, getEvent: function(event){ return event || window.event; }, getTarget: function(evetn){ return event.target || event.srcElement; }, preventDefault: function(event){ if(event.preventDefault){ event.preventDefault(); }else{ event.returnValue = false; } }, stopPropagation: function(event){ if(event.stopPropagation){ event.stopPropagation(); }else{ event.cancelBubble = true; } } }; ``` 我给工具类EventUtil添加了4个新方法,第一个是getEvent(),返回对event对象的引用;第二个是getTarget(),返回事件的目标;第三个是preventDefault(),用于取消事件的默认行为;第四个是stopPropagation(),用来取消事件冒泡。 调用方式也很简单: ``` div.onclick = function(event){ event = EventUtil.getEvent(event); target = event.getTarget(); EventUtil.preventDefault(); EventUtil.stopPropagation(); } ``` **10.2.2 事件处理程序的运行环境** 当通过设置属性注册事件处理程序时,看起来就好像是在文档元素上定义了新方法: ``` e.onclick=function(){} ``` 事件处理程序在事件目标上定义,所以它们作为这个对象的方法来调用。也就是说,在事件处理程序内,this关键字指向事件目标。 **10.2.3 事件处理程序的作用域** 事件处理程序在其定义的作用域而非调用时的作用域中执行,并且它们能存取那个作用域中的任何一个本地变量。 **10.2.4 事件处理程序的返回值** 通过设置对象属性或HTML属性注册事件处理程序的返回值有时是非常有意义的。通常情况下,返回值false就是告诉浏览器不要执行这个事件相关的默认操作。比如,表单提交按钮的onclick事件处理程序能返回false阻止浏览器提交表单。 ``` v.onclick = function() { return false; } ``` 理解事件处理程序的返回值只对通过属性注册的处理程序才有意义。 **10.2.5 调用顺序** 文档元素或其他对象可以指定事件类型注册多个事件处理程序。当适当的事件发生时,浏览器必须按照下面的规则调用所有的事件处理程序: - 通过设置对象属性或HTML属性注册的处理程序一直优先调用。 - 使用addEventListener()注册的处理程序按照它们的注册顺序调用。 - 使用attachEvent()注册的处理程序可能按照任何顺序调用,所以代码不应该依赖于调用顺序。 **10.2.6 事件传播** 当事件目标是Window对象或其他一些单独对象(比如XMLHttpRequest)时,浏览器会简单的通过调用对象上适当的处理程序响应事件。 在调用在目标元素上注册的事件处理函数后,大部分事件会“冒泡”到DOM树根。 发生在文档元素上的大部分事件都会冒泡,但有些例外,比如focus、blur和scroll事件。文档元素上的load事件会冒泡,但它会在Document对象上停止冒泡而不会传播到Window对象。只有当整个文档都加载完毕时才会触发window对象的load事件。 当事件目标是文档或文档元素时,它会在不同的DOM节点之间传播(propagation)。 分为三个阶段: - 捕获阶段(capture phase):从window对象传导到目标对象。(window--document--....--目标对象) - 目标阶段(target phase):目标对象本身的事件处理程序调用。 - 冒泡阶段(bubbling phase):从目标对象传导回window对象。(目标对象--父元素--....--document--window) ```
点击
//事件捕获阶段,click事件的传播顺序 window document
// 目标阶段 div // 事件冒泡阶段,click事件的传播顺序
document window ``` **事件代理(事件委托)** 基于事件会在冒泡阶段向上传播到父节点,我们可以将子节点的监听事件定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。 ```
123
document.getElementById('div').addEventListener('click', function(e) { var target = e.target; if(target.getAttribute('id').toLowerCase() == 'item') { alert(1); } }); ``` 如果使用事件代理,以后插入的新节点仍然可以监听的到。 如果使用JQuery,我们要为新增节点添加事件,除了在新增事件后添加事件外,还可以用下面的代码: ``` $(document).on('click','div',function(){}) ``` 这种方式其实就是使用了事件代理。 **10.2.7 事件取消** 用属性注册的世界处理程序的返回值能用于取消事件的浏览器默认操作。在支持addEventListener()的浏览器中,也能通过调用事件对象的preventDefault()方法取消事件的默认操作。 在IE9之前的IE中,可以通过设置事件对象的returnValue属性为false来达到同样的效果。 ``` function cancelHandler(event){ var event = event || window.event; if(event.preventDefault) {event.preventDefault();} //标准 if(event.returnValue) { event.returnValue = false;} // IE return false; //用于处理使用对象属性注册的处理程序 } ``` Event对象提供了一个属性defaultPrevented,返回一个布尔值,默认false,表示该事件是否调用过preventDefault方法。 **取消事件传播** 在支持addEventListener()的浏览器中,可以调用事件对象的一个stopPropagation()方法以阻止事件的继续传播。 ``` e.stopPropagation() //IE e.cancelBubble = true; ``` 在Event对象上还有一个方法`stopImmediatePropagation()`,阻止同一个事件的其他监听函数被调用。也就是说,如果同一个节点对于同一个事件指定了多个监听函数,这些函数会根据添加的顺序依次调用。只要其中有一个监听函数调用了stopImmediatePropagation方法,其他的监听函数就不会再执行了。 ``` e.addEventListener('click',function(event){ event.stopImmediatePropagation(); }); e.addEventListener('click',function(event){ //不会触发 }); ``` **10.3 文档事件** **(1)beforeunload事件、unload事件、load事件、error事件、pageshow事件、pagehide事件** **beforeunload** 当浏览器将要跳转到新页面时触发这个事件。如果事件处理程序返回一个字符串,那么它将出现在询问用户是否想离开当前页面的标准对话框中。 ``` window.addEventListener('beforeunload',function(e){ var message = '你确认要离开吗!'; e.returnValue = message; return message }); ``` **unload** unload事件在窗口关闭或者document对象将要卸载时触发,发生在window、body、frameset等对象上面。 它的触发顺序排在beforeunload、pagehide事件后面。unload事件只在页面没有被浏览器缓存时才会触发,换言之,如果通过按下“前进/后退”导致页面卸载,并不会触发unload事件。 **load、error** `load`事件直到文档加载完毕时(包括所有图像、JavaScript文件、CSS文件等外部资源)才会触发。 `error`事件在页面加载失败时触发。注意,页面从浏览器缓存加载,并不会触发load事件。 这两个事件实际上属于进度事件,不仅发生在document对象,还发生在各种外部资源上面。浏览网页就是一个加载各种资源的过程,图像(image)、样式表(style sheet)、脚本(script)、视频(video)、音频(audio)、Ajax请求(XMLHttpRequest)等等。这些资源和document对象、window对象、XMLHttpRequestUpload对象,都会触发load事件和error事件。 **pageshow、pagehide** 默认情况下,浏览器会在当前会话(session)缓存页面,当用户点击“前进/后退”按钮时,浏览器就会从缓存中加载页面。 pageshow事件在页面加载时触发,包括第一次加载和从缓存加载两种情况。如果要指定页面每次加载(不管是不是从浏览器缓存)时都运行的代码,可以放在这个事件的监听函数。 pageshow事件有一个persisted属性,返回一个布尔值。页面第一次加载时,这个属性是false;当页面从缓存加载时,这个属性是true。 pagehide事件与pageshow事件类似,当用户通过“前进/后退”按钮,离开当前页面时触发。 pagehide事件的event对象有一个persisted属性,将这个属性设为true,就表示页面要保存在缓存中;设为false,表示网页不保存在缓存中,这时如果设置了unload事件的监听函数,该函数将在pagehide事件后立即运行。 **(2)DOMContentLoaded事件、readystatechange事件** `DOMContentLoaded`事件:当文档加载解析完毕且所有延迟(deferred)脚本(图片未加载完毕)都执行完毕时会触发,此时图片和异步(async)脚本可能依旧在加载,但是文档已经为操作准备就绪了。也就是说,这个事件,发生在load事件之前。 ``` document.addEventListener('DOMContentLoaded',handler,false); ``` `readystatechange`事件:document.readyState属性会随着文档加载过程而变,而每次状态改变,Document对象上的readystatechange事件都会触发。 ``` document.onreadystatechange = function() { if(document.readyState == 'complete'){ } } ``` **(3)scroll事件、resize事件** `scroll`事件在文档(window)或文档元素滚动时触发,主要出现在用户拖动滚动条。 `resize`事件在改变浏览器窗口大小时触发,发生在window、body、frameset对象上面。 **(4)hashchange事件、popstate事件** hashchange事件在URL的hash部分(即#号后面的部分,包括#号)发生变化时触发。如果老式浏览器不支持该属性,可以通过定期检查location.hash属性,模拟该事件。 popstate事件在浏览器的history对象的当前记录发生显式切换时触发。注意,调用history.pushState()或history.replaceState(),并不会触发popstate事件。该事件只在用户在history记录之间显式切换时触发,比如鼠标点击“后退/前进”按钮,或者在脚本中调用history.back()、history.forward()、history.go()时触发。 **(5)cut事件、copy事件、paste事件** 这三个事件属于文本操作触发的事件。 - cut事件:在将选中的内容从文档中移除,加入剪贴板后触发。 - copy事件:在选中的内容加入剪贴板后触发。 - paste事件:在剪贴板内容被粘贴到文档后触发。 这三个事件都有一个clipboardData只读属性。该属性存放剪贴的数据,是一个DataTransfer对象。 **(6)焦点事件** 焦点事件发生在Element节点和document对象上。 - focus事件:Element节点获得焦点后触发,该事件不会冒泡。 - blur事件:Element节点失去焦点后触发,该事件不会冒泡。 - focusin事件:Element节点将要获得焦点时触发,发生在focus事件之前。该事件会冒泡。Firefox不支持该事件。 - focusout事件:Element节点将要失去焦点时触发,发生在blur事件之前。该事件会冒泡。Firefox不支持该事件。 这四个事件的事件对象,带有target属性(返回事件的目标节点)和relatedTarget属性(返回一个Element节点)。对于focusin事件,relatedTarget属性表示失去焦点的节点;对于focusout事件,表示将要接受焦点的节点;对于focus和blur事件,该属性返回null。 由于focus和blur事件不会冒泡,只能在捕获阶段触发,所以addEventListener方法的第三个参数需要设为true。 **10.4 鼠标事件** **(1)click** click事件当用户在Element节点、document节点、window对象上,单击鼠标(或者按下回车键)时触发。 “鼠标单击”定义为,用户在同一个位置完成一次mousedown动作和mouseup动作。它们的触发顺序是:mousedown首先触发,mouseup接着触发,click最后触发。 **(2)contextmenu** contextmenu事件在一个节点上点击鼠标右键时触发,或者按下“上下文菜单”键时触发。 可以通过下面的方式阻止“上下文菜单”的出现: ``` document.oncontextmenu=function(){ return false; } ``` **(3)dblclick** dblclick事件当用户在element、document、window对象上,双击鼠标时触发。该事件会在mousedown、mouseup、click之后触发。 **(4)mousedown、mouseup** mouseup事件在释放按下的鼠标键时触发。 mousedown事件在按下鼠标键时触发。 **(5)mousemove** mousemove事件当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次代码。 **(6)mouseover、mouseenter** mouseover事件和mouseenter事件,都是鼠标进入一个节点时触发。 两者的区别是,mouseenter事件只触发一次,而只要鼠标在节点内部移动,mouseover事件会在子节点上触发多次。 **(7)mouseout、mouseleave** mouseout事件和mouseleave事件,都是鼠标离开一个节点时触发。 除了“mouseenter”和“mouseleave”外的所有鼠标事件都能冒泡。链接和提交按钮上的click事件都有默认操作且能够阻止。可以取消上下文菜单事件来阻止显示上下文菜单。 传递给鼠标事件处理程序的事件对象有clientX和clientY属性,它们指定了鼠标指针相对于包含窗口的坐标。加入窗口的滚动偏移量可以把鼠标位置转换成文档坐标。 **MouseEvent对象的属性** **(1)button、buttons** `button`属性指定当事件发生时哪个鼠标按键按下。 - -1:没有按下键。 - 0:按下主键(通常是左键)。 - 1:按下辅助键(通常是中键或者滚轮键)。 - 2:按下次键(通常是右键)。 buttons属性返回一个3个比特位的值,表示同时按下了哪些键。它用来处理同时按下多个鼠标键的情况。 - 1:二进制为001,表示按下左键。 - 2:二进制为010,表示按下右键。 - 4:二进制为100,表示按下中键或滚轮键。 同时按下多个键的时候,每个按下的键对应的比特位都会有值。比如,同时按下左键和右键,会返回3(二进制为011)。 注意:IE中的button属性拥有不同的参数: - 1:鼠标左键 - 4:鼠标中键 - 2:鼠标右键 **(2)clientX,clientY** `clientX`属性返回鼠标位置相对于浏览器窗口左上角的水平坐标,单位为像素,与页面是否横向滚动无关。 `clientY`属性返回鼠标位置相对于浏览器窗口左上角的垂直坐标,单位为像素,与页面是否纵向滚动无关。 **(3)movementX,movementY** - movementX属性返回一个水平位移,单位为像素,表示当前位置与上一个mousemove事件之间的水平距离。在数值上,等于currentEvent.movementX = currentEvent.screenX - previousEvent.screenX。 - movementY属性返回一个垂直位移,单位为像素,表示当前位置与上一个mousemove事件之间的垂直距离。在数值上,等于currentEvent.movementY = currentEvent.screenY - previousEvent.screenY。 **(4)screenX,screenY** `screenX`属性返回鼠标位置相对于屏幕左上角的水平坐标,单位为像素。 `screenY`属性返回鼠标位置相对于屏幕左上角的垂直坐标,单位为像素。 **(7)pageX、pageY** `pageX`和`pageY`分别是触点相对HTML文档左边沿的X坐标和触点相对HTML文档上边沿的Y坐标。只读属性。 当存在滚动的偏移时,pageX包含了水平滚动的偏移,pageY包含了垂直滚动的偏移。 **(6)relatedTarget** `relatedTarget`属性返回事件的次要相关节点。对于那些没有次要相关节点的事件,该属性返回null。 **10.5 鼠标滚轮事件** 所有的现代浏览器都支持鼠标滚轮,并在用户滚动滚轮时触发事件。浏览器通常使用鼠标滚轮滚动或缩放文档,但可以通过取消mousewheel事件来阻止这些默认操作。 所有浏览器都支持“mousewheel”事件,但Firefox使用“DOMMouseScroll”事件。 传递给“mousewheel”处理程序的事件对象有wheelDelta属性,其指定用户滚动滚轮有多远(根据这个判断滚动方向)。 远离用户方向的一次鼠标滚轮“单击”的wheelDelta值通常是120,而接近用户方向的一次“单击”的值是-120。返回的总是120的倍数(120表明mouse向上滚动,-120表明鼠标向下滚动) 在Safari和Chrome中,为了支持使用二维轨迹球而非一维滚轮的Apple鼠标,除了wheelDelta属性外,事件对象还有wheelDeltaX和wheelDeltaY,而wheelDelta和wheelDeltaY的值一直相同。 而在Firefox中,传递给“DOMMouseScroll”的属性是detail。不过, detail属性值的缩放比率和正负符号不同wheelDelta,detail值乘以-40和wheelDelta值相等。记录其滚动距离的是“detail”属性,它返回的是3的倍数(3表明mouse向下滚动,-3表明mouse向上滚动)。 ``` window.onmousewheel = document.onmousewheel = scrollWheel; function scrollWheel(e){ e = e || window.event; if(e.wheelDelta) { //判断浏览器IE,谷歌滑轮事件 if(e.wheelDelta > 0) { //当滑轮向上滚动时 } else if(e.wheelDelta < 0) { //当滑轮向下滚动时 }; } else if(e.detail) { //Firefox滑轮事件 if(e.detail < 0) { //当滑轮向上滚动时 } else if(e.detail > 0) { //当滑轮向下滚动时 }; }; } ``` **10.6 键盘事件** 键盘事件用来描述键盘行为,主要有keydown、keypress、keyup三个事件。 keydown:按下键盘时触发该事件。 keypress:只要按下的键并非Ctrl、Alt、Shift和Meta,就接着触发keypress事件。 keyup:松开键盘时触发该事件。 textinput 任何时候,只要用户输入文本都会触发。在Webkit浏览器中支持“textInput”事件。 事件对象属性data(保存输入文本),inputMethod属性(用于指定输入源) 注意:keypress和textinput事件是在新输入的文本真正插入到聚焦的文档元素前触发的。 如果用户一直按键不松开,就会重复触发keydown、keypress,直到用户松开才会触发keyup。 **属性** **keycode** 指定了输入字符的编码。在Firefox中使用的是charCode属性。 **altKey,ctrlKey,metaKey,shiftKey** altKey、ctrlKey、metaKey和shiftKey属性指定了当事件发生时是否有各种键盘辅助键按下。 altKey属性:alt键 ctrlKey属性:key键 metaKey属性:Meta键(Mac键盘是一个四瓣的小花,Windows键盘是Windows键) shiftKey属性:Shift键 key,charCode key属性返回一个字符串,表示按下的键名。如果同时按下一个控制键和一个符号键,则返回符号键的键名。比如,按下Ctrl+a,则返回a。如果无法识别键名,则返回字符串Unidentified。 主要功能键的键名(不同的浏览器可能有差异):Backspace,Tab,Enter,Shift,Control,Alt,CapsLock,CapsLock,Esc,Spacebar,PageUp,PageDown,End,Home,Left,Right,Up,Down,PrintScreen,Insert,Del,Win,F1~F12,NumLock,Scroll等。 charCode属性返回一个数值,表示keypress事件按键的Unicode值,keydown和keyup事件不提供这个属性。注意,该属性已经从标准移除,虽然浏览器还支持,但应该尽量不使用。 **String.fromCharCode()** 一个keypress事件表示输入的单个字符。事件对象以数字Unicode编码的形式指定字符,所以必须用String.fromChatCode()把它转换成字符串。 **10.7 表单事件** **(1)input、propertychange** 检测文本输入元素的value属性改变,这两个事件是在新输入的文本真正插入到聚焦的文档元素前触发的。 一般用``和`