JavaScript中的DOM与事件

280 查看

javascript和html之间的交互是通事件实现的。事件就是文档或者浏览器窗口中发生的一些特定的瞬间。我们可以使用侦听器来预定事件,以便事件发生时执行相应的代码.传统的软件工程中称为观察者模式,使得行为(js)和外观(html和css)松散耦合.

DOM事件流

"DOM2级事件"规定事件流包括3个阶段:

  1. 事件捕获(为截获事件提供了机会)
  2. 处于目标(实际的目标接收到事件)
  3. 事件冒泡(可对事件做出响应)

以下面的DOM结构为例:

事件流

单击div元素的时候会按照上述顺序触发事件。

事件处理程序
<button onclick="show();">

思考:上述的时间处理程序有什么缺点?
答:缺点有2个。①可能存在时差问题。考虑这样一种情形。show函数定义在按钮的后面,当DOM还没有加载到script标签的时候将会发生错误,此种情形可以通过内部添加try-catch来解决:<button onclick="try{show();}catch(ex){}">;②拓展事件处理程序的作用链将会是浏览器不兼容的.

DOM0级事件处理程序

该种方式将一个函数赋值给一个事件处理程序的属性,比较简单,兼容性好,例如:

var btn = getElementById('btn')
btn.onclick = function () {
  console.log('clicked',this.id); // clicked btn
}

使用DOM0级方法指定的时间处理程序被认为是元素的方法.这个时候的事件处理程序是在元素作用域中运行(this指向当前元素).以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理,删除事件处理程序只需要将事件处理程序的属性设置为null,例如:btn.onclick = null.

DOM2级事件处理程序

定义了2个方法addEventListener()removeEventListener()用于指定或者删除事件处理程序.都接收3个参数:事件名,事件处理函数和布尔值(true,捕获阶段调用事件处理函数;false,冒泡阶段调用事件处理函数,缺省为false).这种方式可以弥补了DOM0中不能添加多个事件处理函数的不足:

var btn = getElementById('btn')
btn.addEventListener('click',function(){
  console.log(1);
})
btn.addEventListener('click',function(){
  console.log(2);
})
// 1 2

以上为btn添加了2个事件处理程序,这2个程序会按照它们的添加它们顺序触发.

IE事件处理程序

IE9之前的版本支持attachEvent()detachEvent(),接收事件名(注意"on")和事件处理函数,只支持在冒泡阶段处理,和DOM0级事件的区别在于attachEvent()全局作用域中运行,因此window === this,attachEvent也可以绑定多个事件,但是触发的顺序是与添加的时候相反的.

事件对象

兼容DOM的对象会将一个event对象传入到事件处理程序中(无论是DOM0还是DOM2),事件处理程序执行完后,event对象将被销毁;IE中的事件对象是window.event.在事件处理程序内部this === currentTarget,而target则表示的是事件的实际目标,我们来看下面的一个例子:

<body>
    <button type="button" id="btn">按钮</button>
    <script>
        document.body.addEventListener('click',function(e){
            console.log(this); // body
            console.log(e.currentTarget); // body
            console.log(e.target); // button#btn
        });
    </script>
</body>

thiscurrentTarget都是body元素,因为事件是注册到body上的,而target却是按钮,因为它是事件的真是目标(最具体的元素).由于按钮上并没有注册处理程序,click事件就冒泡到了document.body并在那里得到处理.(在上面的例子中我们可以使用e.stopProgation()从而禁止事件传播)

其实,恰当使用能力检测就可以写出兼容代码,详见event.js

事件类型

UI事件

UI事件指的是那些不一定与用户操作有关的事件.

  • load:页面完全加载完成后在window上触发;当所有框架内容加载完毕后在框架集上触发;图像加载完成后在<img>元素上触发;嵌入内容加载完毕后在<object>元素上触发
  • abort:用户停止下载过程时;如果嵌入的内容没有加载完,则在<object>元素上触发
  • error:js错误时在window上触发;图像无法加载时在<img>上触发;无法加载内容时在<object>上触发;一个或者多个框架无法加载时在框架集上触发

根据“DOM2 级事件”规范,应该在document而非window上面触发load事件。但是,所有浏览器都在window上面实现了该事件,以确保向后兼容,例如我们经常用的window.onload.

焦点事件

  • focus,元素获取焦点时触发.但是该事件不会冒泡.
  • blur,元素失去焦点触发,不会冒泡
  • focusin,元素获取焦点时触发,与html事件focus等价,但冒泡.
  • focusout,元素失去焦点时触发,是html事件blur的通用版本.

当焦点从页面中的一个元素移动到另外一个元素,会依次触发下列事件:

  1. focusout在失去焦点的元素上触发
  2. focusin在获得焦点的元素上触发
  3. blur在失去焦点的元素上触发
  4. focus在获得焦点的元素上触发

事件委托

添加太多的事件处理程序会带来性能问题(函数是对象,对象就要占内存),解决方案就是采用事件委托.事件委托利用了事件冒泡,只指定一个事件处理程序就可以管理某一类型的所有事件,例如click事件会一直冒泡到document,也就是说我们可以为整个页面添加一个onclick事件处理程序而不必为每个元素添加事件处理程序,例如:

<ul id="list">
  <li id="li1">li1 content</li>
  <li id="li2">li2 content</li>
  <li id="li3">li3 content</li>
  <li id="li4">li4 content</li>
  <li id="li5">li5 content</li>
</ul>
<script>
  var list = document.getElementById('list')
  list.addEventListener('click',function(ev){
    var target = ev.target
    console.log(target.id)
    switch (target.id) {
        //
    }
  })
</script>