【JavaScript高级程序设计】事件02
本文对01剩下的补充
键盘与输入事件KeyboardEvent
用户操作键盘时触发。很大程度上是基于原始的DOM0实现的。
DOM3 Events为键盘事件提供了一个首先在IE9中完全实现的规范,其他浏览器也开始实现该规范,但仍存在很多遗留的实现。
包含3个事件:
keydown,按下键盘上某个键时触发,持续按钮会重复触发
keypress(DOM3 Events已经废弃,推荐textInput事件),按下键盘上某个键并产生字符时触发,持续按住会重复触发。Esc键也会触发(Chrome里测试不会触发?)。
keyup,用户释放键盘上某个键时触发。
所有元素都支持这些事件。但在文本框中输入内容时最容易看到。
输入事件只有一个:textInput。是对keypress事件的扩展,用于在文本显示给用户之前更方便地截获文本输入。会在文本被插入到文本框之前触发。
1、当用户按下某个字符键时,会触发keydown事件,然后触发keypress事件,最后触发keyup事件。(keydown和keypress会在文本框出现变化之前触发,keyup会在文本框出现变化之后触发);如果按住不放,keydown和keypress会重复触发,直到这个键被释放。
2、当用户按下非字符键时,会触发keydown事件,然后触发keyup事件。如果按住不放,keydown会重复触发,直到这个键被释放。
注:键盘事件支持与鼠标事件相同的修饰键。
键盘事件event对象的一些属性:
1、键码keyCode
keydown和keyup事件,event对象的keyCode属性会保存一个键码。
对于字母和数字键,keyCode的值与大写字母和数字的ASCII编码一致。与是否按了Shift键无关。
DOM和IE的event对象都支持keyCode属性
2、字符编码charCode
keypress事件,意味着按键会影响屏幕上显示的文本。对插入或移除字符的键,所有浏览器都会触发keypress事件,其他键则取决于浏览器。
event对象上的charCode属性,只有发生keypress事件时这个属性才会被设置值(此时与keyCode属性相等)。包含的是按键字符对应的ASCII编码。
IE8及更早版本和Opera使用keyCode传达字符的ASCII编码。要以跨浏览器方式获取字符编码,首先要检测charCode属性是否有值,如果没有再使用keyCode。
有了字母编码,就可以使用String.fromCharCode()方法将其转换为实际的字符了。
DOM3的变化
DOM3 Events规范并未规定charCode属性,而是定义了key和char两个新属性。
key属性用于替代keyCode,且包含字符串。按下字符键时,key等于文本字符;按下非字符键时,key的值是键名(如”Shift“或”ArrowDown“)
char属性在按下字符键时与key类似,在按下非字符键时为null。(测试Chrome中keypress和keydown的event对象此属性都无)
IE支持key属性但不支持char属性。
Safari和Chrome支持keyIdentifier属性(测试Chrome无此属性)。对于字符键,keyIdentifier返回以”U+0000“形式表示Unicode值的字符串形式的字符编码。
由于缺乏跨浏览器支持,不建议使用key、keyIdentifier和char。
DOM3 Events也支持一个名为location的属性,是一个数值,表示是在哪里按的键。可能的值为:0-默认键;1-左边;2-右边;3-数字键盘;4-移动设备(虚拟键盘);5-游戏手柄。Safari和Chrome支持一个等价的keyLocation属性(实现有问题)
没有得到广泛支持,不建议在跨浏览器开发时使用location属性。
给event对象增加了getModifierState()方法,接收一个参数,一个等于Shift、Control、Alt、AltGraph或Meta的字符串,表示要检测的修饰键。如果给定 的修饰键被按钮,则返回true。(也可直接使用event对象的shiftKey、ctrlKey、altKey或metaKey属性获取)
输入textInput事件
DOM3 Events规范新增,在字符被输入到可编辑区域时触发。
与keypress比对:1. keypress会在任何可以获得焦点的元素上触发,textInput只在可编辑区域上触发;2. textInput只在有新字符被插入时才会触发,而keypress对任何可能影响文本的键都会触发(包括退格键(Chrome里测试不会触发?))。3. 使用输入法(搜狗)时,在触发合成事件时不会触发keypress,在compositionend触发之前会先触发textInput事件。4. 使用键盘输入先触发keypress,再触发textInput。
该事件主要关注字符,event对象上有data属性,为被插入的字符(非字符编码);还有一个inputMethod的属性(Chrome中测试无此属性打印为undefined),表示向控件中输入文本的手段,可以辅助验证
设备上的键盘事件(非键盘)
任天堂Wii
合成事件
DOM3 Events中新增,用于处理通常使用IME输入时的复杂输入序列。IME可以让用户输入物理键盘上没有的字符,通常需要按下多个键才能输入一个字符,合成事件用于检测和控制这种输入。
合成事件有以下3种:
compositionstart,表示输入即将开始
compositionupdate,在新字符插入输入字段时触发;
compositionend,表示恢复正常键盘输入
唯一增加的事件属性是data:
在compositionstart中,为正在编辑的文本(默认是空串,或者是选中的文本)
在compositionupdate中,为要插入的新字符
在compositionend中,为本次合成过程中输入的全部内容
测试得到的触发顺序:
keydown -> ...start -> ...update -> ( (keyup) -> keydown -> ...update -> (keyup))(循环触发) -> ...update(此时data与...end事件中的data一致) -> textInput -> ...end -> keyup
变化事件
DOM2的变化事件(Mutation Events),在DOM发生变化时提供通知。
(已废弃)
已经被Mutation Observers所取代(第14章)
HTML5事件
HTML5中得到浏览器较好支持的一些事件(规范未涵盖)
contextmenu事件
单击鼠标右键(Ctrl+单击左键)。用于允许开发者取消默认的上下文菜单并提供自定义菜单。冒泡。
事件目标是触发操作的元素,这个事件在所有浏览器中都可以取消(event.preventDefault()或event.returnValue设置为false)。
通常通过onclick事件处理程序触发隐藏(自定义菜单)。
beforeunload事件
在window上触发。用于给开发者提供阻止页面被卸载的机会。在页面即将从浏览器中卸载时触发。
不能取消,否则就意味着可以把用户永久阻拦在一个页面上。
该事件会向用户显示一个确认框。用户可以点击取消或者确认离开页面。需要将event.returnValue设置为要在确认框中显示的字符串(对于IE和FF来说)(测试FF显示的提示文字与returnValue属性值无关),并将其作为函数值返回(对于Safari和Chrome来说)(测试Chrome无返回值也无影响)
DOMContentLoaded事件
会在DOM树构建完成后立即触发,而不用等待图片、JavaScript文件、CSS文件或其他资源加载完成。(可以在外部资源下载的同时指定事件处理程序,从而让用户能够更快地与页面交互)
比对load事件:要等待很多外部资源加载完成。
需要给document或window添加事件处理程序(实际的事件目标是document,会冒泡到window)。
通常用于添加事件处理程序或执行其他DOM操作。这个事件始终在load事件之前触发。
对于不支持DOMContentLoaded事件的浏览器,可以使用超时为0的setTimeout()函数,通过其回调来设置事件处理程序。本质上是在当前JavaScript进程执行完毕后立即执行这个回调。(与DOMContentLoaded触发时机一致无绝对把握,最好是页面上的第一个超时代码)
readystatechange事件
IE首先定义。用于提供文档或元素加载状态的信息,但行为有时不稳定。
event.target或其他支持readystatechange事件的对象都有一个readyState属性,该属性可能为以下5个值:
- uninitialized:对象存在并尚未初始化
- loading:对象正在加载数据
- loaded:对象已经加载完数据
- interactive √:对象可以交互,但尚未加载完成
- complete √:对象加载完成
并非所有对象都会经历所有readyState阶段(Chrome测试document只经历了两个阶段:interactive和complete)
值为”interactive“的readyState阶段,时机类似于DOMContentLoaded。进入交互阶段,意味着DOM树已加载完成。(此时图片和其他外部资源不一定都加载完成了)。
与load事件共同使用时,这个事件的触发顺序不能保证。interactive和complete的顺序也不是固定的,为了抢到较早的时机,需要同时检测交互阶段和完成阶段(可以保证尽可能接近使用DOMContentLoaded事件的效果)。
pageshow与pagehide事件
FF和Opera开发的一个名为往返缓存(bfcache,back-forward cache)的功能,旨在使用浏览器”前进“和”后退“按钮时加快页面之间的切换。不仅存储页面数据,也存储DOM和JavaScript状态,实际上是把整个页面都保存在内存里。
如果页面在缓存中,导航到这个页面时就不会触发load事件。
pageshow:在页面显示时触发,无论是否来自往返缓存。新加载的页面,会在load事件之后触发;来自往返缓存的页面,会在页面状态完全恢复后触发。事件目标是document,但事件处理程序必须添加到window上。(点击了浏览器的”刷新“按钮,页面会重新加载)。event对象中的persisted属性为布尔值,表示页面内容是否来自往返缓存。
pagehide:在页面从浏览器中卸载后,在unload事件之前触发。事件目标是document,但事件处理程序必须添加到window上。event对象中的persisted属性为布尔值,表示页面在卸载后是否保存在往返缓存中。
注册了onunload事件处理程序的页面会自动排除在往返缓存之外(测试beforeunload也会影响),因为onunload的典型场景就是撤销onload事件发生时所做的事情,如果使用往返缓存,下一次页面显示时就不会触发onload事件,这可能导致页面无法使用。
hashchange事件
用于在URL散列值(#后面的部分)发生变化时通知开发者。
事件处理程序必须添加给window。event对象有两个新属性:oldURL和newURL,分别保存变化前后的URL,包含散列值的完整URL。如果想确定当前的散列值,最好使用location对象。
设备事件
智能手机和平板计算机=>交互的新方式
用于确定用户使用设备的方式。
orientationchange事件
苹果,移动Safari浏览器。判断用户的设备是处于垂直模式还是水平模式。window.orientation属性,有3种值:0-垂直模式,90-左转水平模式(Home键在右),-90-右转水平模式(Home键在左)。当属性值改变就会触发该事件。
所有iOS设备都支持该事件和该属性。(测试锁定竖屏=>不会改变)
被认为是window事件,也可给body元素添加onorientationchange属性来指定事件处理程序。
deviceorientation事件
DeviceOrientationEvent规范定义的事件。
如果可以获取设备的加速计信息,且数据发生了变化,就会在window上触发。只反应设备在空间中的朝向,与移动无关。
设备本身处于3D空间,x轴方向为从设备左侧到右侧,y轴方向为从设备底部到上部,z轴方向为从设备背面到正面。
event对象包含各个轴相对于设备静置时坐标值的变化,主要有5个属性:
alpha:0~360内的浮点值,表示围绕z轴旋转时y轴的度数(左右转)
beta:-180~180内的浮点值,表示围绕x轴旋转时z轴的度数(前后转)
gamma:-90~90内的浮点值,表示围绕y轴旋转时z轴的度数(扭转)。
absolute:布尔值,表示设备是否返回绝对值。
compassCalibrated:布尔值,表示设备的指南针是否正确校准。
测试iPhone8(iOS11.4.1)平放在桌面上也一直监听到变动(?),测试Android(一加三)平放在桌面上后不会变动
devicemotion事件
DeviceOrientationEvent规范定义的事件。
用于提示设备实际上在移动,而不仅仅是改变了朝向。event对象包含的额外属性:
acceleration:对象,包含x、y和z属性,反映不考虑重力情况下各个维度的加速信息
accelerationIncludingGravity:对象,包含x、y和z属性,反映各个维度的加速信息,包含z轴自然重力加速度
interval:毫秒,距离下次触发事件的时间。此值在事件之间应为常量。
rotationRate:对象,包含alpha、beta和gamma属性,表示设备朝向。
如果无法提供acceleration、accelerationIncludingGravity、rotationRate信息,则属性值为null。=> 使用之前必须先检测
测试iPhone8(iOS11.4.1)平放在桌面上也一直监听到变动,测试Android(一加三)平放在桌面上也一直监听到变动
触摸及手势事件
只适用于触屏设备。
Webkit为Android定制了很多专有事件,成为了事实标准,并被纳入W3C的Touch Events规范。
触摸事件
如下几种:
touchstart:手指放到屏幕上时触发
touchmove:手指在屏幕上滑动时连续触发。在此事件中调用preventDefault()可以阻止滚动(测试并不能)
touchend:手指从屏幕上移开时触发
touchcancel:系统停止跟踪触摸时触发。文档未明确什么情况下停止跟踪。
都会冒泡,都可以被取消。不属于DOM规范,浏览器以兼容DOM的方式实现它们。每个触摸事件的event对象都提供了鼠标事件的公共属性,另外提供以下3个属性用于跟踪触点:
touches:Touch对象的数组,表示当前屏幕上的每个触点。
targetTouches:Touch对象的数组,表示特定于事件目标的触点。
changedTouches:Touch对象的数组,表示自上次用户动作之后变化的触点
每个Touch对象包含一些属性,可用于追踪屏幕上的触摸轨迹。(针对一个触点)touchend事件触发时touches集合中什么也没有,这是因为没有滚动的触点了。
当手指点触屏幕上的元素时,依次触发的事件(测试与书本不一致):
1)touchstart
2)touchend
3)mousemove
4)mousedown
5)mouseup
6)click
手势事件
iOS2.0中的Safari中增加。在两个手指触碰屏幕且相对距离或旋转角度变化时触发。有如下3种:
gesturestart:一个手指在屏幕上,再把另一手指放到屏幕上时触发
gesturechange:任何一个手指在屏幕上的位置发生变化时触发
gestureend:其中一个手指离开屏幕时触发都会冒泡。
只有在两个手指同时接触事件接收者时(目标元素边界以内),这些事件才会触发。
触摸事件和手势事件存在一定的关系。
每个手势事件的event对象都包含所有标准的鼠标事件属性,新增了两个属性是rotation和scale。
rotation:表示手指变化旋转的度数,负值表示逆时针旋转,正值表示顺时针旋转(从0开始);
scale:表示两指之间距离变化(对捏)的程度,开始时为1,然后随着距离增大或缩小相应地增大或缩小。
触摸事件也会返回rotation和scale属性,但只在两个手指触碰屏幕时才会变化。
其他一些规范中定义的浏览器事件
参考书本
内存与性能
在JavaScript中,页面中事件处理程序的数量与页面整体性能直接相关。
首先,每个函数都是对象,都占用内存空间;其次,为指定事件处理程序所需访问DOM的次数会先期造成整个页面交互的延迟。
改善页面性能?
事件委托
”过多事件处理程序“的解决方案是使用事件委托。
利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。只要给所有元素(需要处理某种事件的元素)共同的祖先节点添加一个事件处理程序,就可以解决问题(根据target判断进行不同的处理)。=> 只访问了一个DOM元素和添加了一个事件处理程序。 => 占用内存更少,所有使用按钮的事件(大多数鼠标事件和键盘事件)都适用于这个解决方案。
只要可行,就应该考虑只给document添加一个事件处理程序,通过它处理页面中所有某种类型的事件。优点如下:
- document对象随时可用。=> 只要页面渲染出可点击的元素,就可以无延迟地起作用。
- 既可以节省DOM引用,也可以节省时间(设置页面事件处理程序的事件)。
- 减少整个页面所需的内存,提升整体性能。
最适合使用事件委托的事件包括:click、mousedown、mouseup、keydown和keypress。
删除事件处理程序
把事件处理程序指定给元素后,在浏览器代码和负责页面交互的JavaScript代码之间就建立了联系。这种联系建立得越多,页面性能就越差。除了使用事件委托减少这种联系外,还应及时删除不用的事件处理程序。
很多Web应用性能不佳都是由于无用的事件处理程序长驻内存导致的。原因如下:
删除带有事件处理程序的元素。如使用方法removeChild()或replaceChild()删除节点,或使用innerHTML整体替换页面的某一部分。=> 被删除的元素上若有事件处理程序,就不会被垃圾收集程序正常清理。(特别是IE8及更早版本,元素的引用和事件处理程序的引用)
如果知道某个元素会被删除,那么最好在删除它之前手工删除它的事件处理程序(或者不直接给它添加事件处理程序,使用事件委托)。=>确保内存被回收,元素也可以安全地从DOM中删掉。
注意:在事件处理程序中删除元素会阻止事件冒泡。只有事件目标仍然存在于文档中时,事件才会冒泡。
页面卸载导致内存中残留引用。事件处理程序没有被清理,会残留在内存中。
最好在onunload事件处理程序中趁页面尚未卸载先删除所有事件处理程序。=> 使用事件委托的优势:事件处理程序很少。
模拟事件
通常事件都是由用户交互或浏览器功能触发。
可以通过JavaScript在任何时候触发任意事件 => 在测试Web应用时特别有用
DOM3规范指明了模拟特定类型事件的方式。
DOM事件模拟
步骤:
1、使用document.createEvent()方法创建一个event对象。
createEvent()方法接收一个参数,一个表示要创建事件类型的字符串。DOM2是英文复数形式,DOM3中是英文单数形式。可用值为以下之一:
”UIEvents“(DOM3是”UIEvent“):通用用户界面事件(鼠标事件和键盘事件都继承于此)
”MouseEvents“(DOM3是”MouseEvent“):通用鼠标事件
”HTMLEvents“(DOM3中无):通用HTML事件(已分散到其他事件大类中)
键盘事件(DOM3 Events中增加)
“Events”:通用事件
“CustomEvent”(DOM3中增加):自定义事件
2、使用事件相关的信息来初始化
每种类型的event对象都有特定的方法,取决于调用createEvent()时传入的参数
3、触发事件
事件目标调用dispatchEvent()方法。该方法存在于所有支持事件的DOM节点上。
接收一个参数,即要触发事件的event对象
4、冒泡并触发事件处理程序执行
不同事件类型的模拟:
鼠标事件
调用createEvent()并传入”MouseEvents“参数
调用返回的event对象的initMouseEvent()方法,为新对象指定鼠标的特定信息。接收15个参数,分别对应鼠标事件会暴露的属性,如type、bubbles、cancelable、view等,这四个是正确模拟事件唯一重要的几个参数,因为浏览器要用到,其他参数则是事件处理程序要用的。
event对象的target属性会自动设置为调用dispatchEvent()方法的节点
所有鼠标事件都可以在DOM合规的浏览器中模拟出来
键盘事件
DOM3中创建键盘事件的方式是给createEvent()方法传入参数”KeyboardEvent“
调用返回的event对象的initKeyboardEvent()方法。接收8个参数,包括type、bubbles、cancelable、view等。
DOM3 Events中废弃了keypress事件,因此只能通过上述方式模拟keydown和keyup事件。
在使用document.createEvent("KeyboardEvent")之前,最好检测一下浏览器对DOM3键盘事件的支持情况document.implementation.hasFeature("KeyboardEvents", "3.0")。
测试Chrome,调用initKeyboardEvent()方法传入的key和modifier参数与在事件处理程序中打印出来的属性不一致,可以使用new KeyboardEvent()(参数与在事件处理程序中打印出的一致),另,两种模拟都不会使文本框中有内容
FF限定:
给createEvent()传入”KeyEvents“来创建键盘事件
调用event对象的initKeyEvent()方法。接收10个参数,包括type、bubbles、cancelable、view等。
测试:ff(88.0)显示不支持 Uncaught DOMException: Operation is not supported
其他不支持键盘事件的浏览器:
创建一个通用的事件,给createEvent()方法传入参数”Events“
调用event对象的initEvent()方法
通过event.xxx方式指定特定于键盘的信息
必须使用通用事件而不是用户界面事件,因为用户界面事件不允许直接给event对象添加属性
其他事件
HTML事件:
调用createEvent()方法并传入“HTMLEvents”
调用返回的event对象的initEvent()方法来初始化信息
测试:模拟focus,能监听到事件,但是没有光标
自定义DOM事件
DOM3新增自定义事件类型。不触发原生DOM事件。
调用createEvent()并传入参数“CustomEvent”
调用返回的event对象的initCustomEvent()方法,接收4个参数:type、bubbles、cancelable、detail
调用dispatchEvent()
IE事件模拟
在IE8及更早版本中模拟事件。
步骤:
- 使用document对象的createEventObject()方法来创建event对象。不接收参数,返回一个通用event对象
- 手工给返回的对象指定希望该对象具备的所有属性。(无初始化方法)可指定任何属性,包括IE8及更早版本不支持的属性。这些属性值对于事件来说并不重要,只有事件处理程序才会使用它们。
- 在事件目标上调用fireEvent()方法。接收2个参数:事件处理程序的名字和event对象。srcElement和type属性会自动指派到event对象。
IE支持的所有事件都可以通过相同的方式来模拟。
小结
最常见的事件是在DOM3 Events规范或HTML5中定义的。
需要考虑内存与性能问题:
限制页面中事件处理程序数量。=> 避免占用过多内存导致页面响应慢,清理起来更方便
- 使用事件委托
- 在页面卸载或元素从页面删除前,清理掉相关的事件处理程序