事件
JS 与 HTML 之间交互便是通过事件实现的。事件就是文档或者浏览器窗口中发生的一些特定的交互瞬间。可以使用监听器来预定事件,以便事件发生的时候执行相应的代码。
1. 事件流
事件流描述的是从页面中接收时间的顺序,IE的为冒泡流,而Netscape为事件捕获流。
1.1 事件冒泡
那么事件流的顺序按照冒泡为
div -> body -> html -> Document
需要注意的是 只有 IE9, FF, Chrome 和 Safari 会一直冒泡到 window
1.2 事件捕获
事件捕获则是一个相反的流程
Document ->html -> body -> div
依次从上到下传递事件流。、
1.3 DOM 事件流
DOM2级事件 中规定事件流包括3各阶段:事件捕获,处理目标和事件冒泡。
对于IE,直到IE9才支持该DOM2级事件。
2. 事件处理程序
事件就是用户或者浏览器自身执行某种动作,响应某个事件的函数就称之为事件处理程序。
2.1 HTML事件处理程序
该操作通过指定 onclick 特性并将一些 JS 代码作为它的值来进行定义。
需要注意的地方就是 HTML事件中 this 的指向问题
如上的 input 代码 当 onclick="alert(this.value)",没有问题会弹出 "Click Me" 的结果。
但是一旦 onclick="showValue()"再定义
function showValue(){
alert(this.value);
}
结果为 undefined。 因为这里的this 已经指向到 window,如果需要将函数定义在外部全局中,并且需要使用到所点击的节点DOM,那么就在HTML的JS代码中传入 this 即可,onclick="showValue(this)",在处理函数。
2.2 DOM 0级事件处理程序
通过JS指定事件处理程序传统方式就是将函数赋给一个事件处理程序属性。简单又有跨浏览器优势。
函数内部的 this 会指向当前元素
删除事件只需要将事件属性置为 null 即可。
2.3 DOM 2级事件处理程序
DOM 2级事件 定义了2个方法, addEventListener 和 removeEventListener 。参数有三个, 处理的事件名,事件函数以及是否是捕获阶段的布尔值。
在此的事件函数是使用匿名函数的方式传入,所以就不能在使用 removeEventListener 来解除绑定了,因为就算传入了一样的匿名函数,也是指向不同的函数,所以希望使用 remove 的,必须在add事件的时候使用变量引用传入。
还有就是对于一个元素的一个属性上可以绑定多个事件,并且是按照绑定的顺序执行的。addEventListener添加的事件只能使用 removeEventListener 来解绑,不能使用 0级事件处理程序的方式来解除事件绑定。
(IE9, FF,Safari, Chrome, Opera 支持 DOM2级)
2.4 IE事件处理程序
IE实现的与DOM中类似的两个方法 attachEvent 和 detachEvent 接受两个参数 事件处理程序名称 和 事件处理程序函数。 其第一个参数为 on 开头,如 onclick,onsubmit等。
在IE中使用attachEvent与使用DOM0级方法最大的区别是事件处理程序的作用域。DOM0级中事件处理程序会在所属元素的作用域内运行,但是在 attachEvent 方法下,事件处理程序会在全局作用于下运行,this会指向于window。
在对于编写跨浏览器的代码时,记住这点很重要。
还有一个与标准的DOM2级不同的是,在一个元素添加多个事件处理程序的时候,addEventListener是按照绑定的顺序先绑定的先执行原则触发事件处理程序的,但是 attachEvent 是先执行最近绑定的事件的,即执行顺序上与 addEventListener 相反。
在点击后,先弹出 “Helloworld!”,而后 “Clicked”。
2.5 跨浏览器的事件处理程序
这边只是简单的提供了一个兼容方案,当然没有很细致的考虑其他的比如IE中事件处理程序的作用域问题,以及绑定多个事件的执行顺序问题。
3. 事件对象
在触发DOM上的某个事件的时候,会产生一个事件对象event,该对象中包含了所有与事件有关的信息。例如鼠标点击时刻鼠标的位置,键盘按下有关于键的信息等。
3.1 DOM中的事件对象
兼容DOM的浏览器不管使用 DOM0级还是DOM2级都会将 event 事件对象传入。
event 对象包含与创建他的特定时间有关的属性和方法,触发的事件类型不一样可用的属性和方法也不一样,不过所有的又会有以下属性:
在事件处理程序内部,this 始终指向 currentTarget的值, 对于 target 只包含事件的实际目标,当直接将事件处理程序指定给目标元素,那么三个值就是相等的。
如下,将事件处理程序绑定在 body上
也可以看做提高效率的 事件委托 的雏形。
event 事件对象中 preventDefault() 来取消默认行为,比如 a 标签的跳转行为等,stopPropagation() 方法使得事件停止在DOM中的传播,取消进一步的事件捕获或者冒泡。
注意,只有在事件处理程序执行期间event事件对象才会存在,一旦事件结束,event对象就会销毁。
3.2 IE中的事件对象
之前所说的均是标准DOM下的方式, IE对于事件对象有一定的不同,但是总体上是一致的。在DOM 0级下,event对象作为window的一个属性存在
event并不是直接存在于事件处理程序之中的,而需要获取到 window.event 再进行使用。在attenchEvent中,event对象就会被作为参数传入事件处理程序之中。
IE的event同样也会包含一些属性和方法
因为事件处理程序的作用域是更具指定他的方式来确定的,所有不能认为this会始终等于时间目标,使用 event.srcElement 比较保险。
3.3 跨浏览器的事件对象
虽然 DOM 与 IE 下event对象有所不同,但是基于相似性还是可以实现跨浏览器的解决方案来的。
4. 事件类型
DOM3级规定了以下
UI事件,当用户与页面上的元素交互的时候触发。
焦点事件,当元素获得或者失去焦点的时候触发。
鼠标事件,当用户通过鼠标在页面上执行操作的时候触发。
滚轮事件,当用户鼠标滚轮时触发。
文本事件,当文档中输入文本的时候触发。
键盘事件。
合成事件,当为IME 输入法编辑器 输入字符的时候触发。
变动事件,当低层的DOM结构发生变化的时候触发。
DOM3级事件模块在DOM2级事件模块的基础上重新定义了这些事件,也添加了新的事件,IE9在内主流浏览器均已支持DOM2级事件,IE9也支持部分DOM3事件。
4.1 UI事件
DOMActivate:表示元素已经被用户操作过(DOM3中已经废弃)。
load:当页面完全加载后在window上面触发,当所有的框架都加载完毕的时候在框架集上触发,当图像加载完毕的时候在上触发,以及嵌入的
unload:页面完全卸载的时候在window上面触发。。。
abort:用户停止下载过程,内容还没有加载完毕的时候。
error:当JS在window上发生错误的时候,图像无法加载的时候等。
select:当用户选着文本框中的一个或者多姿字符时候触发。
resize:窗口或者框架大小变化的时候触发。
scroll:用户滚动带滚动条的内容的时候触发。
需要注意的是,resize和scroll事件会在我们调整窗口大小或者滚动的时候重复被触发,所以应该尽量保持事件处理程序的代码简单,或者做好逻辑处理。
4.2 焦点事件
blur:当元素失去焦点的时候触发,事件不冒泡,各个浏览器兼容。
focus:当元素获得焦点的时候触发,事件不冒泡,各个浏览器兼容。
focusin:当元素获得焦点的时候触发,事件冒泡,各个浏览器兼容(IE5.5+ SF5.1+ Op 11.5+ chrome)。
focusout:当元素失去焦点的时候触发,事件冒泡,各个浏览器兼容(IE5.5+ SF5.1+ Op 11.5+ chrome)。
那么当焦点从一个元素移动到另外一个元素的时候会依次触发
1. focusout 在失去焦点的元素上触发
2. focusin 在获得焦点的元素上触发
3. blue 在失去焦点的元素上触发
4. focusout 在获得焦点的元素上触发
( Opera 上 还有DOMFocusIn 和 DOMFocusOut 事件 )
4.3 鼠标与滚轮事件
click: 用户单击鼠标按钮时,或者按下回车键的时候。
dblclick: 用户双击鼠标时候触发。知道DOM3才将其纳入标准。
mousedown:用户按下任意鼠标按钮,不可以通过键盘触发。
mouseup:用户释放任意鼠标按钮,不可以通过键盘触发。
mouseenter:在鼠标光标从元素外部移到元素范围内的时候触发,事件不冒泡,而且在移动到后代元素上的时候不触发,DOM未定义该事件,DOM3纳入规范(IE,FF 9+,Opera等均支持)。
mouseleave:在鼠标光标从元素内部移到元素范围外的时候触发,事件不冒泡,而且在移动到后代元素上的时候不触发,DOM未定义该事件,DOM3纳入规范(IE,FF 9+,Opera等均支持)。
mousemove:当鼠标在元素内部移动的时候重复的触发。不能通过键盘触发。
mouseout:鼠标在一个元素上方,然后首次移入到另外一个元素的时候触发。
mouseover:鼠标在一个元素外部,然后首次移入到该元素上方的时候触发。
在双击鼠标的时候
mousedown -> mouseup -> click -> mousedown -> mouseup -> click ->dblclick
在对于IE8之前的版本来说会有一个小BUG, 双击时候会跳过第二个 mousedown 和 click。
检测是否支持DOM2 DOM3级
document.implementation.hasFeature("MouseEvents", "2.0");
document.implementation.hasFeature("MouseEvent", "3.0");
还有一个跟踪鼠标滚轮的事件 mousewheel 。
1. 客户区坐标位置
鼠标事件都是在浏览器视口中特定位子发生的,位子信息保存在事件对象的 clientX 与 clientY 属性中。
可以使用下面代码获取
该位置为鼠标相对于客户端视口的位子,不包括页面滚动的距离,并不表示鼠标在页面上的位置。
2. 页面坐标位置
页面位置通过事件对象的 pageX 和 pageY 属性来获取,此坐标为页面本身而非视口的左边与顶边计算的。
因此在页面没有滚动的情况下,clientX,clientY 与 pageX,pageY的值是相等的。
IE8以及早期的版本不支持事件对象的页面坐标,不过可以通过客户区坐标和滚动信息计算出来,需要使用 scrollLeft和scrollTop。
3. 屏幕坐标位置
还有一个相对于屏幕的位子坐标信息 screenX,screenY。
代码如下:
4. 修改键
鼠标事件主要是由鼠标来触发的,但是也有可能按下了其他键。 状态为 shiftKey,ctrlKey,altKey和metaKey。
IE8及之前版本不支持。
5. 相关元素
相关元素指的是鼠标在发生 mouseover 和 mouseout 的时候还会涉及到其他元素。
DOM通过event 的 relatedTarget 属性提供相关元素的信息,只对 mouseover 和 mouseout 事件才包含,对于其他事件均为 null。
IE8之前版本没有relatedTarget 属性,可以使用fromElement 和 toElement 来获取相等意义的元素。
6. 鼠标按钮
DOM的button 属性
0:主鼠标键; 1:鼠标中间键或者滚轮键; 2:次鼠标键。
IE8及以前浏览器
7. 更多的事件信息
DOM2级事件规范还在event对象中提供了 detail 属性,用于给出更多的事件信息。
对于鼠标事件来说,detail中包含了一个数值表示在给定的位子上发生了多少次的点击。
8. 鼠标滚轮事件
从IE6起,就支持 mousewheel 事件,当用户通过鼠标滚轮与页面交互,在垂直方向上滚动页面的时候都会触发 mousewheel 事件。在滚动的时候还包含一个特殊的 wheelDelta 属性,记录的滚动的量值,为120的倍数向前为正,向后为负。Firefox 支持一个名为 DOMMouseScroll的类似事件,信息保存在detail属性中,量值为3的倍数。向前为负,向后为正。
9. 触摸设备
由于iOS, 安卓等设备较为特殊,没有鼠标所以
1. 不支持 dbclick,双击放大画面,无法改变该行为。
2. 轻击可单击元素会触发 mouseover 事件,如果此操作会导致内容变化将不再有其他事件发生。如果没有变化 一次会触发 mousedown mouseup 和 click。
3. mousemove 也会触发 mouseover 和 mouseout 事件。
4. 两个手指在屏幕上且页面随手指滚动的时候也会触发 mousewheel 和 scroll 事件。
10. 无障碍性问题
4.4 键盘与文本事件
DOM3级事件 为键盘事件制订了规范
只有一个文本事件 textInput,是对keypress 的补充,使得将文本展现给用户之前可以拦截文本,在文本插入文本框之前就会触发 textInput。
1. 键码
keydown 和 keyup 的事件 event 对象的 keyCode 属性中包含一个代码,来显示所按下的。
2. 字符编码
IE9 以及其他主流浏览器的 event 对象都支持一个 charCode,代表按下键的 ASCII编码。
3. DOM3级变化
DOM3的键盘事件不再包含 charCode 属性, 而是分开为 key 和 char 的两个新属性。
4. textInput 事件
当用户在可编辑区域中输入字符的时候就会触发该事件,任何可获得焦点的元素都可以触发 keypress,而是由可编辑区域才可以触发 textInput。
4.5 复合事件
是DOM3中的新添加的一类事件用于处理 IME 输入序列。可以让用户输入键盘上没有的符号。
4.6 变动事件
DOM2级变动事件能在DOM的某一部分发生变化的时候来给出提示。
对于浏览器支持情况来说
1. 删除节点
在使用removeChild() 或者 replaceChild() 从DOM中删除节点的时候首先会触发 DOMNodeRemoved事件。该事件的 event.target 就是被删除的节点。
而 event.relatedNode则是对目标节点父节点的引用。
在上面HTML中,我们删除 ul 元素,就会依次:
1. 在 ul 上触发 DOMNodeRemoved 事件,relatedNode 属性等于 document.body。
2. 在 ul 上触发 DOMNodeRemovedFromDocument 事件。
3. 在 ul 下的 每一个 li 上 触发DOMNodeRemovedFromDocument 事件。
4. 在 document.body 上触发 DOMSubtreeModified 事件。
验证代码:
2. 插入节点
在使用appendChild,replaceChild 和 insertBefore 向DOM中插入节点的时候首先会触发 DOMNodeInserted 事件。
首先触发的是 DOMNodeInserted 事件,该事件的目标是被插入的节点,相同的 event.relatedNode 属性中包含的是对父节点的引用。并且该事件是冒泡的。
其次为在新插入的节点上触发 DOMNodeInsertedIntoDocument 事件,该事件不冒泡。事件的目标为被插入的节点,所以需要在该节点插入之给节点添加好事件处理程序。
4.7 HTML 5 事件
DOM规范没有涵盖所有的浏览器支持的事件许多浏览器推出了自己的特殊事件。HTML5详尽的列出了浏览器应该支持的所有事件。
1. contextmenu 事件
为了实现上下文菜单,windows下是鼠标右键单击的菜单,如何操作该事件,于是就有了contextmenu 事件,用以表示何时该显示上下文菜单,以便开发人员自定义上下文菜单。
JS 实现自定义上下文菜单
基本浏览器均已支持。
2. beforeunload 事件
window对象上的事件处理函数, 是为了让开发人员有可能在页面卸载之前来阻止该操作。
除了 opera11之前版本,其余均以支持。
3. DOMContentLoaded 事件
window的load事件会在页面中的一切都加载完毕之后触发,而DOMContentLoaded 事件则在完整的DOM数之后就触发,不会受到图片,JS文件,css文件等影响,意味着客户可以更早的与页面交互。
支持的浏览器有 IE9+ FF Chrome SF Op9+ 。
对于不支持的浏览器建议页面加载期间设置一个事件为 0ms 的setTimeout 来进行模拟处理。即在当前JS处理完毕后立即运行该函数。
4. readystatechange 事件
IE 为 DOM 文档中的某些部分提供了 readystatechange 事件,目的是为了提供与文档或者元素的加载状态相关的信息,支持readystatechange 的每个对象还有一个 readyState 属性:
1. uninitialized 未初始化 :对象存在但是尚未初始化
2. loading 正在加载 : 对象正在加载数据
3. loaded 加载完毕 :对象加载数据完成
4. interactive 交互 : 可以操作对象但是还没有完全加载
5. complete 完成 : 对象已经加载完毕
状态很直观,但是并非所有的对象都会经历 readyState 的这些阶段。
对于 document 来说, 值为interactive 的readyState 会与DOMContentLoaded 大致相同,但是不能保证先后顺序。
支持readystatechange的有 IE 、Firefox 4+ 与 Opera。
还有一个需要注意的是
我们要检测加载完毕的时候需要一并检测 readyState的两个状态,并且在事件调用了一次之后便移除该事件。因为有些对象会值出现某一个状态,而有些会出现两个状态。
5. pageshow 和 pagehide 事件
页面显示,卸载的时候触发。有时当我们使用页面的前进后退按钮,会调用页面的缓存而不会触发页面的load,而 pageshow 则不会影响。其对象为 window。而pagehide则与 beforeunload类似。
兼容的浏览器 Firefox,Safari 5+,chrome 和 opera。IE9及之前版本均为支持。
6. hashchange
HTML5 新增的 hashchange 事件,再URL的参数列表(url中 #后部分)发生变化的时候触发通知开发人员。主要用于在ajax应用中开发人员经常需要利用 url参数列表来保存状态或者导航信息。当然也是需要将该事件处理程序添加给 window 对象。
event对象中会有两个属性 oldURL 和 newURL,保存了变化前后各自的URL。可以使用以下代码来检测
var isSupported = ( "onhashchange" in window ) && ( document.documentMode === undefined || document.documentmode > 7 );
4.8 设备事件
设备事件 device event 可以让开发人员确定用户在怎样的使用设备,W3C从2011年开始就在制定关于设备时间的新草案,以涵盖不断增长的设备类型并且定义相关事件。
1. orientationchange 事件
苹果公司为一定 safari 添加orientationchange 事件,方便开发人员对于横向与竖向查看模式的处理。
window.orientation 属性中包含3个值,0 表示肖像模式,90 代表左旋转横向模式 -90代表右旋转横向模式,文档中还有 180 代表倒置,但还未支持。
只要设备更改了查看模式就会触发 orientationchange 事件,event中不包含任何有价值的信息,唯一信息可以通过 window.orientation 访问到。
2. MozOrientation 事件
Firefox 3.6 为检测设备的方向引入了该事件。与iSO的 orientationchange 事件不同,该事件只能提供一个平面方向的变化。
event 对象中包含3个值, 想x,y,z处于 1 至 -1 之间,竖直状态下 x:0 ,y:0, z:1如果设备向右倾斜,x 会减小,反之向左倾斜x增大。向远离用户方向倾斜 y减少,接近方向倾斜则会增大。z表示垂直上的加速度,1表示静止不动,失重状态下为0 (为实验性的API)。
3. deviceorientation事件 与 devicemotion事件。
4.9 触摸 与 手势事件
touchstart : 当手指触摸屏幕触发,即是已经有手指放在屏幕上了也会触发。
touchmove:在手指滑动时候连续触发。
touchend:当手指从屏幕上移开的时候触发。
touchcancel:当系统停止跟踪触摸时触发。
以上的均可以冒泡以及取消。除了常见的DOM属性之外,触摸事件还包括3个用于跟踪触摸的属性。
touches:表示当前跟踪的触摸操作Touch对象数组。
targetTouches:特定于时间目标的Touch对象数组。
changeTouches:上次触摸以来发生改变了的Touch对象数组。
其中每个 Touch 对象包含一下属性。
clientX:触摸目标在视口中的x坐标。
clientY:触摸目标在视口中的y坐标。
identifier:标识触摸的唯一id。
pageX:触摸目标在页面中的x坐标。
pageY:触摸目标在页面中的y坐标。
screenX:触摸目标在屏幕中的x坐标。
screenY:触摸目标在屏幕中的y坐标。
target:触摸的DOM节点目标。
在触摸屏幕上的元素时,事件触发程序顺序如下。
1. touchstart
2. mouseover
3. mousemove 一次
4. mousedown
5. mouseup
6. click
7. touchend
对于手势事件
gesturestart:当一个手指已经在屏幕上,另外一个手指触摸屏幕的时候触发。
gesturechange:当触摸屏幕的任何一个手指位子发生变化的时候触发。
gestureend:当任何一个手指从屏幕上移开的时候触发。‘
5. 内存与性能
JS中添加到引入面上的事件处理程序数量将直接关系到页面的整体运行性能,首先每个函数都是对象会占用内存,内存占用越多性能就会越差,其次指定事件的处理程序导致的对DOM访问次数会延迟整个页面的交互就绪时间。
5.1 事件委托
对于 事件处理程序过多 的问题,采用的解决方案就是 事件委托。
事件委托利用冒泡,指定一个事件处理程序来管理某一类型的所有事件。
对于列表项点击触发事件,可以逐一的对各项 li 进行事件处理程序的绑定,但是一旦 li 项较多的时候就会有很大的问题,事件委托就是将事件处理程序绑定在外层的 ul 上(也可以是更外层的元素上),进行 event 中 target 判断来执行代码。
如果可行可以考虑在document对象上添加一个处理程序,用以处理页面上发生的某种特定类型事件。
1. document 对象很快就可以访问,可以在页面生命周期的任何时间点添加处理程序甚至无需等待DOMContentloaded 或者 load 事件。
2. 页面的设置事件处理程序所需时间更少。
3. 整个页面占用的内存空间更少,提升整体性能。
最适合采用的委托事件有 click,mousedown,mouseup,keydown,keyup和 keypress。
5.2 移除事件处理程序
每当将事件处理程序指定给元素的时候,运行中的浏览器代码和支持页面交互的JS之间就会建立一个连接,连接越多页面就越慢在不需要的时候移除那些不需要的事件处理程序也能改善页面整体性能。
第一种情况需要注意的是,从文档中移除带有事件处理程序的元素时,可能通过纯粹的DOM操作例如 removeChild 和 replaceChild 方法,但更多的是使用 innerHTML 来替换页面中的某一部分,如果是使用 innerHTML来删除了那么原来的事件处理程序极有可能将无法被当做垃圾回收。
还有一种情况就是在卸载页面的时候,对于IE8以及之前版本浏览器依旧有很多问题。在页面卸载之前没有清理干净事件处理程序,那么他们将依旧滞留在内存之中。在我们切换页面以及刷新等操作的时候就会不断地消耗内存。
6. 模拟事件
很少人知道可以使用JS在任意时刻来触发特定事件,而此时的事件如同浏览器创建事件一样,在测试web应用程序的时候模拟出发时间是一种极其有用的技术。DOM2规范了此模拟事件的方式。IE9,Opera,Firefox,chrome和safari。