《JavaScript高级程序设计》Chapter 13 事件
小记
JS与HTML之间的交互通过事件实现,事件发生在交互的瞬间,可以使用事件监听器(或者事件处理程序)来预定时间。DOM2事件模块尽量对事件进行规范,然而DOM3又增加了一些额外的处理方式,再加上BOM和浏览器之间的差异性,事件处理有的时候会十分的复杂。但仍然需要了解基本的概念。
导航
- 事件流的概念 go
- 事件处理程序(HTML、DOM0、DOM2以及IE的大体运作方式,跨浏览器处理) go
- 事件对象(event。DOM和IE的差别,跨浏览器处理)go
- 事件类型(列举各种常用事件类型,了解一些常用的类型)go
- 内存与性能(考虑性能问题和内存占用情况,减少使用事件的数量并及时清除,重点是事件委托)go
- 模拟事件 go
事件流
- 纸上画同心,当指向其中一个圆的时候实际上指向了所有的圆,那么如何确定指向的顺序,同理于事件,因此对时间流进行规范。
- 事件流用以描述从页面接受事件的顺序。
- 主流:事件冒泡。IE。向上传递事件。
- 其他:事件捕获。Netscape。与事件冒泡顺序相反,主要是为了捕获事件。
- 完整的DOM事件流(DOM2级规定):事件处理有三个阶段:事件捕获、处于目标、事件冒泡阶段。如图:
事件处理程序
- 事件(click)--响应事件的函数:事件处理程序/事件侦听器(如onclick)
- 分为HTML事件处理程序、DOM0事件处理程序、DOM2事件处理程序、IE事件处理程序,因此最后需要讨论跨浏览器的处理。
- HTML事件处理程序,将事件处理程序作为元素的属性,并在其值中执行JS代码。
- JS代码注意一些转义字符的处理。
- 事件处理程序的代码可以访问全局作用域中的任何代码。
- 优点:扩展作用域的方式,可以通过event、this来访问元素对象,并可以通过表单的name值来访问其他表单的字段。
- 缺点:1、时差-可通过try-catch解决;2、不同浏览器对其中的作用域链处理方式不同;3、平稳退化的问题。
- DOM0级事件处理。
- 元素通过其属性进行事件处理:element.onclick = function(){dosomething();};
- 是元素的方法,即函数内部的this指向这个元素。
- 删除方式: element.onclick = null;
- DOM2级事件处理
- addEventListener()/removeEventListener()用于处理和删除事件
- 3个参数:待处理事件名、处理的函数、布尔值(true--捕获阶段调用事件处理;false--冒泡阶段调用)
- 作用域:this同样指向调用的元素。
- 优点:可以绑定多个事件处理程序。而DOM0中不支持。这些程序按照绑定顺序执行。
- 注意:removeEventListener()中的参数需要与addEventListener()中的完全一样才能够有效的解除事件,因此若事件处理函数是匿名函数,则无法处理。
- 一般不会在事件捕获阶段注册事件处理程序。
- IE事件处理程序
- attachEvent()/detachEvent():2个参数:事件处理程序名(!!与DOM2不同,不是click型而是onclick型)、事件处理程序函数。默认为冒泡阶段注册事件处理程序。
- 作用域:在全局作用域中运行,this指向window。
- 同样可以为一个元素绑定多个事件处理程序。这些程序按照逆序执行。
- detachEvent()同样需要传入一样的参数,同样无法处理匿名函数的情况。
- 跨浏览器的事件处理程序
- 利用JS库
- 恰当使用能力检测:PDF P372。将绑定和解绑函数写在一个对象中(之后还要添加一系列的跨浏览器事件对象的方法和属性);主要关注冒泡阶段。仍有不足,但够用。
事件对象(event等)
- 分为DOM中事件对象和IE中对象,因此还考虑了跨浏览器的情况。
- event在事件处理程序执行的过程中才存在。一旦事件处理程序执行完成,event就被销毁。
- DOM(无论DOM0还是DOM2)处理事件处理程序的时候都会传入event对象。
- 具有一些属性和方法
- 注意this、currentTarget和target:在事件处理程序内部,this始终等于currentTarget,指向事件程序注册的位置,而target指向按钮。
- event.type属性可以确定事件类型:click、mouseover、mouseout等
- 阻止默认行为:event.preventDefault();(或者return false?)当event.cancelable属性为true的时候才可以生效。
- 阻止冒泡或者捕获阶段:event.stopPropagation();
- 确定事件执行阶段:event.eventPhase属性。
- IE中的对象
- 获取event:window.event或者(当用attachEvent()添加事件orHTML属性访问事件的时候)event
- 有一些属性和方法,功能等同DOM中的。
- srcElement代替target
- 阻止默认行为:returnValue = false,相当于preventDefault。
- 阻止冒泡:cancelBubble = true 相当于stopPropagation(),不过只能阻止冒泡。
- 跨浏览器的事件对象,完善上一节中的那个对象。见PDF 379页。
事件类型
- DOM3级事件模块在DOM2级事件模块的基础上进行更新,具体规定了下列的一些事件
- UI事件:用户与页面上的元素交互时触发
- 焦点事件
- 鼠标事件
- 滚轮事件
- 文本事件:在文档中输入文本的时候触发
- 键盘事件
- 合成事件:当为IME(Input Method Editor)输入字符时触发
- 变动事件:底层DOM结构发生变化的时候触发
- 变动名称事件:废除了
- 另外HTML5定义了一组事件
- 浏览器会在BOM和DOM上实现一些专有事件
- UI事件:多与window对象或者表单控件有关,多在DOM2中被归为HTML事件。
var isSupported = document.implementation.hasFeature("HTMLEvents", "2.0"); var isSupported = document.implementation.hasFeature("UIEvent", "3.0");
- load事件:
- 两种定义onload事件处理程序的方式:通过调用JS代码(可调用上一节中的跨浏览器事件对象处理)、在HTML的属性中处理(对应于window的话需要在<body>元素上指定)。
- 可在window(document)上指定,实际上,在给其他元素(如img、link、script等)指定onload事件之前一般需要确保window的onload事件已经执行。
- 一般来说,DOM2级事件要求在<body>元素而非window对象上绑定onload事件,然而所有浏览器都在window上实现了onload事件,以便向后兼容。
- 注意到<img>在指定onload事件的时候,在指定src属性之前先指定事件。一般来说,新图像不一定要从添加文档之后才下载,只要设置了src属性就会开始下载。(注意image对象的处理)
- <script><link>元素同样可以指定onload事件,而他们在指定了src/href属性并添加到文档之后才会开始下载。一般可以用来确定脚本或者外部样式表是否加载完毕。
- unload事件:顾名思义,只要用户从一个页面切换到另一个页面,即触发。可用于解除引用,防止内存泄漏。onunload事件大体上的使用同onload事件。但注意由于unload是解绑后触发,而这个时候它绑定的元素或者对象不一定存在了,需要小心处理。
- resize事件:顾名思义。在window对象或者<body>元素上绑定,因此event.target值会是document。由于各个浏览器对其的处理方式不同,可能导致频繁的重复触发,所以注意不要传入过多代码。
- sroll事件:在window对象或者<body>元素上绑定,实际上会反应在元素or容器的位置上,可以在混杂模式下通过body元素的scrollLeft/scrollTop监控,或者标准模式下(除了Safari)通过html来进行监控。同样可能频繁重复的触发,所以传入代码需谨慎。
- load事件:
-
焦点事件
var isSupported = document.implementation.hasFeature("FocusEvent","3.0");
- 会在页面获得或者失去焦点的时候触发,与document.hasFocus()方法以及document.activeElement属性配合可以知晓用户动态。
- blur/focus:元素失去/获得焦点,不会冒泡。
- DOMFocusOut/DOMFocusIn:元素失去/获得焦点,会冒泡是HTML事件blur/focus的通用版本。Opera使用,DOM3级事件弃用。
- focusout/focusin:等价于HTML事件blur/focus,会冒泡,相对于上面一条的事件,得到更广泛的支持。
- 焦点从页面中的一个元素移动到另一个元素上,按以下顺序触发:
- focusout--focusin---blur--DOMFocusOut--focus---DOMFocusIN
- target明显,分别是失去焦点的元素以及获得焦点的元素。
- 鼠标与滚轮事件
var isSupported = document.implementation.hasFeature("MouseEvents","2.0"); var isSupported = document.implementation.hasFeature("MouseEvent","3.0");
-
- click(回车)/dbclick,mousedown/mouseup, mouseenter/mouseleave,mouseover/mouseout; mousewheel(滚轮事件)注意到除了click可以用回车触发之外,其他事件都不能用键盘触发,所以考虑无障碍浏览器的时候避免使用到其他的时间。除了mouseenter和mouseleave之外的时间都会冒泡。
- mousedown/mouseup和click以及dbclick的关系表现为这样的触发顺序:mousedown->mouseup->click->mousedown->mouseup->dbclick
- 一些坐标位置:客户区坐标位置clientX/clientY(鼠标事件触发的时候,鼠标在视口的坐标信息)、页面坐标位置pageX/pageY(鼠标事件触发的时候,鼠标在页面的坐标信息,实际上pageX = clientX+document.body.scrollLeft/document.documentElement.scrollLeft)、屏幕坐标位置screenX/screenY(相对于整个电脑屏幕的位置信息)。
- 修改键:鼠标事件触发的时候event.shiftKey/ctrlKey/altKey/metaKey,为布尔值。
- 相关元素(mouseover和mouseup):概念好理解。属性even.relatedTarget指向这个相关元素。IE中对于mouseover有fromElement属性,mouseup有toElement属性,可以进行跨浏览器处理。PDF P392。
- 鼠标按钮(mousedown或者mouseup):event.button属性的0,1,2分别对应主按钮、滚轮按钮和次按钮,IE8中规定了0-7个数字表示,可以将IE模型规范化为DOM方式,进行跨浏览器处理。PDF 393。
- 更多的事件信息:
- detail属性:记录在同一个像素区域中相继发生一次mousedown和mouseup(单击)的次数。
- IE的altLeft、ctrlLeft、offsetX、offsetY、shifitLeft
- 滚轮事件mousewheel,会冒泡
- 属性wheelDelta记录偏移量:向前为120的倍数、向后为-120的倍数。
- Opera9.5之前(client.engine.opera && client.engine.opera < 9.5 见客户端检测一章),正负号规定相反。
- Firefox的DOMMouseScroll事件功能相同,然而其detail属性在向前滚轮的时候为-3的倍数,向后为3的倍数。
- 综上,可以进行能力检测,实现跨浏览器应用。见PDF P396。
- 触摸设备:触屏。
- 键盘与文本事件
- DOM2级并没有具体定义相关事件,基本上沿用DOM0的,而DOM3定义了一些新的事件。
- 3个键盘事件:keydown(所有键)/keypress(字符键)/keyup。1个文本事件textInput
- 按下字符键,触发顺序:keydown->keypress->keyup
- 按以下非字符键,触发顺序:keydown -> keyup
- 如果按住相应的键不放,keydown或者keypress会一直触发。
- textInput是对keypress的补充,区别有两个:1、前者只对可编辑区域有效,后者对任意可以获得焦点的元素有效。2、前者只在输入确切字符的时候触发,后者在输入类似backspace的时候同样有效。
- 键码(keydown、keyup事件):event.keyCode属性,返回对应键的ASCII码,且与shift状态无关。不同浏览器之间会有一些差异。借助String.fromCharCode()可以将获得的ASCII码转换为实际字符。
- 字符编码(keypress):event.charCode属性,只在keypress事件的时候有效,返回对应的字符的键码,此时event.keyCode属性可能是0或者按键键码。在跨浏览器处理的时候,一般需要确定是否支持charCode,如果不支持的话就使用keyCode。见pdf P400。
- DOM3新增的事件、属性及方法,大多还未普及
- key/char属性(不推荐使用):key表示文本字符(k/K)或者键名(“Shift”),char在按下的是字符的时候同样表示文本字符,其他情况下为空。IE9只支持key,Safari 5和Chrome有替代的keyIdentifier属性对非字符进行同样处理、字符则按照一定格式返回表示Unicode的字符串。此处需要进行跨浏览器处理。见PDF P401。
- location属性(不推荐使用):表示按下的键在键盘上的什么位置或者属于哪种外设。Safari和Chrome有keyLoction替代属性。注意跨浏览器处理。
- 修改键:DOM3新增的getModifierState()方法,然而用DOM属性shiftKey/altKey/ctrlKey/metaKey足够。
- textInput事件:与keypress的差异上面提到过。
- inputMethod属性:表示将文本输入到文本框中的方式。用以检测文本输入到控件中的方式,以验证其有效性。
- 设备中的键盘事件:各种外设。触摸设备。
- 复合事件(DOM3新增,处理IME输入,用处不大)
isSupported = document.implementation.hasFeature("CompositonEvent","3.0");
-
- IME输入:经常用来输入键盘无法直接输入的字符(如日语等),多数情况下需要多个键同时按下以输入某一个字符(因此“复合”)。
- 事件:compositionstart/compositionupdate/compositionend
- 属性:event.data
- 变动事件(DOM结构发生变动)
- 许多事件见名字就知道作用,不再赘述。DOM3废除了许多,举两个删除和添加节点的时候触发变动事件的例子。
- 从DOM结构中删除节点,触发顺序:
- 在删除的节点上触发DOMNodeRemoved事件:event.target是该节点,event.relatedNode与该节点的parentNode属性一样,指向其父节点,冒泡。
- 在这个节点及其所有子节点上触发DOMNodeRemovedFromDocument事件,不冒泡(注意一般测试的时候会测试子节点,而非这个节点,以防止该节点从结构中删除影响绑定事件的发生)。
- 在其父节点上触发DOMSubtreeModified事件。
- 向DOM结构中插入节点,触发顺序:
- 在该节点上触发DOMNodeInserted事件,event.target指向该节点,event.relatedNode同样指向其父节点。冒泡。
- 在该节点上触发DOMNodeInsertedIntoDocument事件,不冒泡,因此必须在插入节点之前为它添加这个事件处理程序。
- 在其父节点上触发DOMSubtreeModifed事件。冒泡。
- HTML5事件(规定一些DOM3没来得及规范的事件)
- contextmenu事件(处理元素的上下文,在对元素右键单击的时候出现):冒泡。鼠标事件(一般右键or Ctrl+单击)。可以屏蔽(用一般的事件方法)。
- beforeunload事件:在页面卸载之前实现一些操作以提供可以阻止用户离开界面的机会。与event.returnvalue结合使用。
- DOMContentLoaded事件:与load事件相比,在完成完整的DOM树之后就会触发,而不理会JS文件、CSS文件等是否已经下载完毕。意义在于:使用户较早的与页面交互。会冒泡。一般都在window(document)上绑定。
- readystatechange事件(IE):提供与文档或元素加载状态相关的信息,一般有readyState属性,可以表示5个状态值(以确认对象的存在及初始化、加载情况)。可以应用在许多元素上以检测其状况,但是元素不一定必然经历5个状态、同时并非所有元素的状态含义都一样,需要实际考虑。
- pageshow和pagehide事件(Firefox、Opera):针对“往返缓存”特性(即加快页面对“后退”和“前进”按钮的响应)。理解bfcache。属性event.persisted表示是否从bfcache中加载得到。
- haschange事件:URL参数列表(包括#后的所有字符串)发生变动的时候触发,主要用在Ajax中,用URL参数数列来保存状态或者导航信息。属性oldURL、newURL可以实现location.hash的功能。一般使用后者,因为支持oldURL和newURL的浏览器不多。
- 设备事件(手机or平板,具体用到再研究)
- orientationchange事件 for IOS(浏览模式:横向还是纵向)
- MozOrientation事件 for Firefox 3.6
- deviceorientation事件
- devicemotion事件
- 触摸与手势事件(for 触屏,具体用到再研究):基本上是在iOS2.0的Safari中引入。
内存和性能
- JS需要慎重使用事件处理程序的数量,是单进程运行的代码,会延迟脚本运行事件。同时对象(函数)越多,占用的内存越大。有一些优化手段:
- 事件委托(解决事件处理程序过多的问题)
- 利用冒泡程序来解决,例如click会冒泡,那么只在DOM树中尽量最高(而不影响功能实现的质量的)层次指定一个处理程序。
- 甚至直接在document上委托事件。
- 移除事件处理程序:在不需要的时候移除过时不同的“空事件处理程序”。还可以帮助处理逻辑上的问题。
- “空事件处理程序”的两个成因:
1、从页面中移除带有事件处理程序的元素时,这个元素漂浮着而无法收回其事件处理程序和一些引用,而这些仍然保存在内存中。
2、卸载页面之前,没有清理事件处理程序,导致滞留对象数目一再增加。 - 解决方法
1、在移除元素前先解除事件处理程序。有效利用事件委托,不直接把事件处理程序添加到需要删除的元素上,而是委托到高层次元素中。
2、卸载页面前,通过onunload事件处理程序移除所有的事件处理程序,要想减轻移除的工作量,可以有效利用事件委托。这么看来,通过onload添加的东西,需要通过onunload删除。
- “空事件处理程序”的两个成因:
模拟事件
- DOM中:document.createEvent()。传入相应参数模拟鼠标、键盘或者其他事件,甚至可以自定义DOM事件。返回对应的方法或者对象来进行进一步的操作。
- IE中:模拟任何事件都具有相同的模式:用document.createEventObject()创建一个event对象--为这个event对象添加属性值和方法进行初始化(一个自定义的过程)---最后通过fireEvent()方法调用这个事件,传入两个参数:事件名称和event对象。