JS的事件

事件流

事件流描述的是从页面中接收事件的顺序

IE 的事件流是事件冒泡流,而 Netscape Communicator 的事件流是事 件捕获流


事件冒泡

即事件开始时由最具体的元素(文档中嵌套层次最深 的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)


事件捕捉

事件捕获的思想 是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。

事件捕获的用意在于在 事件到达预定目标之前捕获它。


DOM事件流

“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。

事件捕获为截获事件提供了机会

冒泡阶段对事件做出响应

在 DOM 事件流中,实际的目标(<div>元素)在捕获阶段不会接收到事件。

这意味着在捕获阶段, 事件从 document 到再到后就停止了。下一个阶段是“处于目标”阶段,于是事件在<div>上发生,并在事件处理(后面将会讨论这个概念)中被看成冒泡阶段的一部分。


事件处理程序

响应某个事件的函数就叫做事件处理程序

HTML事件处理程序

某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的 HTML 特性来指定。

<input type="button" value="Click Me" onclick="alert('Clicked')" />

在 HTML 中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的 脚本

<script type="text/javascript"> 
 function showMessage(){ 
 alert("Hello world!"); 
 } 
</script> 
<input type="button" value="Click Me" onclick="showMessage()" /> 

事件处理程序中的代码在执行时,有权访问全局作用 域中的任何代码

指定事件处理程序具有一些独到之处,这样会创建一个封装着元素属性值的函数。这个 函数中有一个局部变量 event,也就是事件对象


DOM0 级事件处理程序

通过 JavaScript 指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。

原因一是简单,二是具有跨浏览器的优势。

var btn = document.getElementById("myBtn"); 
btn.onclick = function(){ 
 alert(this.id); //"myBtn" 
};

可以在事件处理程序中通过 this 访问元素的任何属性和方法。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。

btn.onclick = null; //删除事件处理程序


DOM2 级事件处理程序

“DOM2级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()removeEventListener()

它们都接受 3 个参数:要处 理的事件名作为事件处理程序的函数和一个布尔值

最后这个布尔值参数如果是 true,表示在捕获 阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。

var btn = document.getElementById("myBtn"); 
btn.addEventListener("click", function(){ 
 alert(this.id); 
}, false); 

为按钮添加了两个事件处理程序这两个事件处理程序会按照添加它们的顺序触发

var btn = document.getElementById("myBtn"); 
btn.addEventListener("click", function(){ 
 alert(this.id); 
}, false); 

btn.addEventListener("click", function(){ 
 alert("Hello world!"); 
}, false);


通过 addEventListener()添加的匿 名函数将无法移除

var btn = document.getElementById("myBtn"); 
btn.addEventListener("click", function(){ 
 alert(this.id); 
 }, false); 
 
 btn.removeEventListener("click", function(){ //没有用!
 alert(this.id); 
}, false);


传入 removeEventListener()中的事件处理程序函数必须与传入 addEventListener()中的相同

var btn = document.getElementById("myBtn"); 
var handler = function(){ 
 alert(this.id); 
}; 

btn.addEventListener("click", handler, false); 

//这里省略了其他代码
btn.removeEventListener("click", handler, false); //有效!

事件对象

在触发 DOM 上的某个事件时,会产生一个事件对象 event

例如,鼠标操作导致的事件 对象中,会包含鼠标位置的信息,而键盘操作导致的事件对象中,会包含与按下的键有关的信息。


DOM中的事件对象

兼容 DOM 的浏览器会将一个 event 对象传入到事件处理程序中

var btn = document.getElementById("myBtn"); 
btn.onclick = function(event){ 
 alert(event.type); //"click" 
}; 

btn.addEventListener("click", function(event){ 
 alert(event.type); //"click" 
}, false); 

currentTarget Element 只读 其事件处理程序当前正在处理事件的那个元素

target Element 只读 事件的目标

在事件处理程序内部,对象 this 始终等于 currentTarget 的值,而 target 则只包含事件的实 际目标。


要阻止特定事件的默认行为,可以使用 preventDefault()方法

例如,链接的默认行为就是在 被单击时会导航到其 href 特性指定的 URL。

只有 cancelable 属性设置为 true 的事件,才可以使用 preventDefault()来取消其默认行为。

stopPropagation()方法用于立即停止事件在 DOM 层次中的传播,即取消进一步的事件 捕获或冒泡。

var btn = document.getElementById("myBtn"); 
btn.onclick = function(event){ 
 alert("Clicked"); 
 event.stopPropagation(); 
}; 

//冒泡的被阻止了
document.body.onclick = function(event){ 
 alert("Body clicked"); 
};

事件对象的 eventPhase属性用来确定事件当前正位于事件流的哪个阶段

在捕获阶 段调用的事件处理程序,在捕获阶 段调用的事件处理程序

事件处理程序处于目标对象上,eventPhase 等于 2

在冒泡阶段调用的事件处理程序,eventPhase 等于 3

只有在事件处理程序执行期间,event 对象才会存在;一旦事件处理程序执行完 成,event 对象就会被销毁。


事件类型

  • UI(User Interface,用户界面)事件,当用户与页面上的元素交互时触发
  • 焦点事件,当元素获得或失去焦点时触发
  • 鼠标事件,当用户通过鼠标在页面上执行操作时触发
  • 滚轮事件,当使用鼠标滚轮(或类似设备)时触发
  • 文本事件,当在文档中输入文本时触发
  • 键盘事件,当用户通过键盘在页面上执行操作时触发
  • 合成事件,当为 IME(Input Method Editor,输入法编辑器)输入字符时触发
  • 变动(mutation)事件,当底层 DOM 结构发生变化时触发

UI事件

  • load

    当页面完全加载后在 window 上面触发

    当所有框架都加载完毕时在框架集上面触发

    当图像加载完毕时在<img>元素上面触发

    当嵌入的内容加载完毕时在元素上面 触发。

  • unload

    当页面完全卸载后在 window 上面触发

    当所有框架都卸载后在框架集上面触发

    当嵌入的内容卸载完毕后在元素上面触发

  • abort

    在用户停止下载过程时,如果嵌入的内容没有加载完,则在元素上面触发

  • error

    当发生 JavaScript 错误时在 window 上面触发

    当无法加载图像时在<img>元素上面触 发

    当无法加载嵌入内容时在元素上面触发

    或者当有一或多个框架无法加载时在框 架集上面触发

  • select

    当用户选择文本框(<input><texterea>)中的一或多个字符时触发

  • resize

    当窗口或框架的大小变化时在 window 或框架上面触发

  • scroll

    当用户滚动带滚动条的元素中的内容时,在该元素上面触发。


load 事件

window上的load

EventUtil.addHandler(window, "load", function(event){ 
 alert("Loaded!"); 
}); 

图像上的load

var image = document.getElementById("myImage"); 

EventUtil.addHandler(image, "load", function(event){ 
 event = EventUtil.getEvent(event); 
 alert(EventUtil.getTarget(event).src); 
}); 

unload事件

利用这个事件最多的情况是清除引用,以避免内存泄漏

EventUtil.addHandler(window, "unload", function(event){ 
 alert("Unloaded"); 
}); 

resize 事件

当浏览器窗口被调整到一个新的高度或宽度时,就会触发 resize 事件

这个事件在 window(窗 口)上面触发

EventUtil.addHandler(window, "resize", function(event){ 
 alert("Resized"); 
}); 

scroll 事件

EventUtil.addHandler(window, "scroll", function(event){ 
 if (document.compatMode == "CSS1Compat"){ 
 alert(document.documentElement.scrollTop); 
 } else { 
 alert(document.body.scrollTop); 
 } 
});

焦点事件

焦点事件会在页面元素获得或失去焦点时触发。利用这些事件并与 document.hasFocus()方法及 document.activeElement 属性配合

有以下 6 个焦点事件

  • blur:在元素失去焦点时触发。这个事件不会冒泡
  • focus:在元素获得焦点时触发。这个事件不会冒泡
  • focusin:在元素获得焦点时触发,但它冒泡
  • focusout:在元素失去焦点时触发,但它冒泡

鼠标与滚轮事件

DOM3 级事件中定 义了 9 个鼠标事件

  • click:在用户单击主鼠标按钮(一般是左边的按钮)或者按下回车键时触发。
  • dblclick:在用户双击主鼠标按钮(一般是左边的按钮)时触发。
  • mousedown:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。
  • mouseenter:在鼠标光标从元素外部首次移动到元素范围之内时触发。这个事件不冒泡,而且 在光标移动到后代元素上不会触发。
  • mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。这个事件不冒泡,而且 在光标移动到后代元素上不会触发。
  • mousemove:当鼠标指针在元素内部移动时重复地触发。不能通过键盘触发这个事件。
  • mouseout:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。
  • mouseover:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触 发。不能通过键盘触发这个事件。
  • mouseup:在用户释放鼠标按钮时触发。不能通过键盘触发这个事件。

除了 mouseenter 和 mouseleave,所有鼠标事件都会冒泡

只有在同一个元素上相继触发 mousedown 和 mouseup 事件,才会触发 click 事件;如果 mousedown 或 mouseup 中的一个被取消,就不会触发 click 事件。

(也就解释为什么网页按钮点击下去但不松开,移到别的地方松开不触发事件了 )

如果有代码阻止了连续两次触发 click 事件,那么就不会触发 dblclick 事件了。

这 4 个事件触发的顺序始终如下

(1) mousedown

(2) mouseup

(3) click

(4) mousedown

(5) mouseup

(6) click

(7) dblclick


mousewheel

当用 户通过鼠标滚轮与页面交互、在垂直方向上滚动页面时(无论向上还是向下),就会触发 mousewheel 事件。这个事件可以在任何元素上面触发,最终会冒泡到 document或 window

与 mousewheel 事件对应的 event 对象除包含鼠标事件的所有标准信息外, 还包含一个特殊的 wheelDelta 属性。

当用户向前滚动鼠标滚轮时,wheelDelta 是 120 的倍数

EventUtil.addHandler(document, "mousewheel", function(event){ 
 event = EventUtil.getEvent(event); 
 alert(event.wheelDelta); 
}); 

键盘与文本事件

有 3 个键盘事件

  • keydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。
  • keypress:当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。
  • keyup:当用户释放键盘上的键时触发。

在文本插入文本框之前会触发 textInput事件。

textInput当用户在可编辑区域中 输入字符时,就会触发这个事件

这个用于替代 keypress 的 textInput 事件的行为稍有不同。

区别 之一就是任何可以获得焦点的元素都可以触发 keypress 事件,但只有可编辑区域才能触发 textInput 事件。

区别之二是 textInput 事件只会在用户按下能够输入实际字符的键时才会被触发,而 keypress 事件则在按下那些能够影响文本显示的键时也会触发(例如退格键)


在用户按了一下键盘上的字符键时,首先会触发 keydown 事件,然后紧跟着是 keypress 事件, 最后会触发 keyup 事件。

var textbox = document.getElementById("myText"); 

EventUtil.addHandler(textbox, "keyup", function(event){ 
 event = EventUtil.getEvent(event); 
 alert(event.keyCode); 
});

通过keyCode来判断键码


事件委托

事件委托利用了事件冒泡,只指定一个事 件处理程序,就可以管理某一类型的所有事件

例如,click 事件会一直冒泡到 document 层次。也就 是说,我们可以为整个页面指定一个 onclick 事件处理程序

<ul id="myLinks"> 
 <li id="goSomewhere">Go somewhere</li> 
 <li id="doSomething">Do something</li> 
 <li id="sayHi">Say hi</li> 
</ul>

包含 3 个被单击后会执行操作的列表项,按照传统的做法,需要像下面这样为它们添加 3 个事 件处理程序。

var item1 = document.getElementById("goSomewhere"); 
var item2 = document.getElementById("doSomething"); 
var item3 = document.getElementById("sayHi"); 

EventUtil.addHandler(item1, "click", function(event){ 
 location.href = "http://www.wrox.com"; 
}); 

EventUtil.addHandler(item2, "click", function(event){ 
 document.title = "I changed the document's title"; 
});

EventUtil.addHandler(item3, "click", function(event){ 
 alert("hi"); 
});

此时,可以利用事件委托技术解决这个问题。使用事件委托,只需在 DOM 树中尽量最高的层次上添加一个事件处理程序

var list = document.getElementById("myLinks"); 

list.addEventListener("click", function(event){ 
    
let target =event.target

 switch(target.id){ 
 case "doSomething": 
 document.title = "I changed the document's title"; 
 break; 
         
 case "goSomewhere": 
 location.href = "http://www.wrox.com"; 
 break; 
         
 case "sayHi": 
 alert("hi"); 
 break; 
         
 } 
});

我们使用事件委托只为<ul>元素添加了一个 onclick 事件处理程序。

由于所有列 表项都是这个元素的子节点,而且它们的事件会冒泡,所以单击事件最终会被这个函数处理。


移除事件处理程序

<div id="myDiv"> 
 <input type="button" value="Click Me" id="myBtn"> 
</div> 

<script type="text/javascript"> 
    
 var btn = document.getElementById("myBtn"); 
 btn.onclick = function(){ 
 //先执行某些操作
 document.getElementById("myDiv").innerHTML = "Processing..."; //麻烦了!
 }; 

</script> 

在元素上设置 innerHTML 可以把按钮移走,但事件处理程序仍然与按钮保持 着引用关系,在这种情况下不会作出恰当地处理,它们很有可能会将对元素和 对事件处理程序的引用都保存在内存中。


正确操作

<div id="myDiv"> 
 <input type="button" value="Click Me" id="myBtn"> 
</div> 

<script type="text/javascript"> 
 var btn = document.getElementById("myBtn"); 
 btn.onclick = function(){ 
     
 //先执行某些操作
 btn.onclick = null; //移除事件处理程序
     
 document.getElementById("myDiv").innerHTML = "Processing..."; 
 }; 
</script>