JavaScript与HTML之间的交互是通过事件实现的。这一章专门讲事件。
事件流描述的是从页面中接收事件的顺序。IE事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕获流。
事件冒泡是指事件开始时由具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点。
事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获他。
所有现代浏览器都支持事件冒泡,但是具体实现上还是有一定差别。老版本的浏览器不支持事件捕获,所有很少有人使用事件捕获。
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段。多数支持DOM事件流的浏览器都实现了一种特定的行为,即使“DOM2事件流”规范明确要求捕获阶段不会设计事件目标,但是IE9、Safari、Chrome、Firefox、Opera9.5以及更高版本都会在捕获阶段触发事件对象上的事件,结果就是有两个机会在目标对象上操作事件。
DOM事件流:
事件处理程序
HTML事件处理程序
在HTML中指定事件处理程序有两个缺点。首先存在一个时差问题。因为用户可能会在HTML元素一出现在页面上就触发相应的事件,但是当时的事件处理程序有可能尚不具备执行条件。为此,很多的HTML事件处理程序都会被封装在一个try-catch块中,以便错误捕获。另一个缺点是这样扩展时间处理程序的作用域链在不同浏览器中会导致不同结果,不同的JavaScript引擎遵循的标识符解析规则略有差异,很可能会在访问非限定对象成员时出错。最后一个缺点是HTML和JavaScript代码紧密耦合,如果要更换事件处理程序,就要改动两个地方。因此,很多开发不用HTML事件处理程序,转而使用JavaScript指定事件处理程序。
DOM0级事件处理程序
通过JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种方式至今仍然为所有现代浏览器所支持。要使用JavaScript指定事件处理程序,首先必须获得一个要操作的对象的引用。使用DOM0级方法指定的事件处理程序被认为是元素的方法,因此这个时候的事件处理程序是在元素的作用域中运行,程序中的this引用当前元素。
1 |
var btn = document.getElementById("myBtn"); |
也可以删除通过DOM0级方法指定的事件处理程序,只要将事件处理程序属性的值设置为null即可。
1 |
btn.onClick = null; |
DOM2级事件处理程序
DOM2级事件定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()和removeEventListener()。所有DOM节点中都包含着两个方法,并且接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。布尔值参数为true表示在捕获阶段调用事件处理程序,如果是false表示在冒泡阶段调用事件处理程序。
1 |
var btn = document.getElementById("myBtn"); |
IE事件处理程序
IE实现和DOM类似的方法,attachEvent()和detachEvent(),接收两个参数:事件处理程序名称和事件处理程序函数。会被添加到事件冒泡阶段。匿名代码同样不能移除。
1 |
var btn = document.getElementById("myBtn"); |
在IE中使用attachEvent()与使用DOM0级方法的主要区别在于事件处理程序的作用域。在使用DOM0级方法的情况下,事件处理程序会在其他所属元素的作用域内进行;在使用attachEvent()方法的情况下,事件处理程序额会在全局作用域中运行,因此this等于window。
跨浏览器事件处理程序
创建addHandler()的职责是视情况分别使用DOM0方法、DOM2方法或者IE方法来添加事件。这个方法属于一个名叫EventUtil的对象,用来处理浏览器之间的差异。addHandler()方法接受三个参数:要操作的元素、事件名称、事件处理程序函数。相对应的方法是removeHandler()。
1 |
var EventUtil = { |
事件对象
DOM中的事件对象
兼容DOM的浏览器会将一个event对象传入到事件处理程序中,无论指定事件处理程序时使用DOM0或者DOM2级方法。
cancelable属性设置为true的事件,可以通过preventDefault()来取消默认行为。另外,stopPropagation()方法用于立即停止事件在DOM层次中的传播,即取消进一步的事件捕获或者冒泡。
事件对象的eventPause属性可以用来确定事件当前正处于事件流的那个阶段,捕获阶段为1,处于目标对象时未2,冒泡阶段为3。
只有事件处理程序执行期间event对象才会存在,一旦事件处理程序执行完毕,event对象就会被销毁。
IE中的事件对象
在使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在。如果事件处理程序是使用attachEvent()添加的,那么就会有一个event对象作为参数被传入事件处理程序函数中,也可以通过window对象访问,如果是通过HTML特性指定的事件处理程序,那么还可以通过event变量访问event对象。
事件类型
DOM3级事件规定了以下几类事件:
- UI事件,当用户与页面上的元素交互使触发
- 焦点事件,当元素获得或者失去焦点时触发
- 鼠标事件,当用户通过鼠标在页面上执行操作时触发
- 滚轮事件
- 文本事件,当在文档中输入文本时触发
- 键盘事件
- 合成事件,当为IME(输入法编辑器)输入字符时触发
- 变动事件,当底层DOM结构发生变化时触发
- 变动名称事件,当元素或者属性名变动时触发,已废弃。
内存和性能
在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存,内存中对象越多,性能越差。其次,必须事先指定所有事件处理程序而导致DOM访问次数,会延迟整个页面的交互就绪事件。
对事件处理程序过多问题的解决办法是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如click事件会一致冒泡到document层级,也就是说,我们可以为整个页面指定一个onClick事件处理程序,而不必给每个单击的元素分别添加事件处理程序。优点在于:
document对象很快就可以访问,而且可以在页面生命周期的任何时间点上为它添加事件处理程序,无需等到DOMContentLoaded或者load事件。换句话说,只要可单击的元素呈现在页面上,就可以立即具备适当的功能。- 在页面中设置事件处理程序所需的事件更少,只添加一个事件处理程序所需的DOM引用更少,所花的时间也更少。
- 整个页面占用的内存空间更少,能够提升整体性能。
每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的JavaScript代码之间就会建立一个连接,这种连接越多,页面执行起来就越慢。可以采用事件委托技术,限制建立的连接数量,另外,在不需要的时候移除事件处理程序,也是一个解决办法。内存中留油那些过时不用的“空事件处理程序”,也是造成web应用程序内存与性能问题的主要原因。
在两种情况下会造成上述问题:第一种情况就是从文档中移除带有事件处理程序的元素时,可能是通过纯粹的DOM操作,例如使用removeChild()和replaceChild()方法。但更多的是发生在使用innerHTML替换页面中某一部分的时候,如果带有事件处理程序的元素被innerHTML删除了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收。第二种情况是卸载页面的时候。如果在页面被卸载之前没有清理干净事件处理程序,那么它们就会滞留在内存中,每次加载完页面再卸载页面时(可能是在两个页面间来回切换,也可能是点击了刷新按钮),内存中滞留的对象数目就会增加,因为事件处理程序占用的内存没有被释放。
一般来说,最好的做法就是在页面卸载之前,先通过onunload事件处理程序移除所有的事件处理程序。在这里,如果使用事件委托,那么要跟踪的事件处理程序越少,移除就越容易。对于这种类似撤销的操作,可以想象成只要是通过onload事件处理程序添加的东西,最后都要通过onunload移除。使用onunload事件处理程序意味着页面不会被换存在bfcache种,如果在意这个问题,那么就只能在IE中通过onunload来移除事件处理程序了。
这一章没有很详细的看,太具体了,对于我这个只是想明白JavaScript运行相关的知识点的人来说参考价值不大,在具体网页开发过程中倒是有必要了解。所以就这样掠过了。




近期评论