17. Js事件处理

208 阅读15分钟

17. Js事件处理

  • 事件类型(event type)是一个用来说明发生什么类型事件的字符串。例如,"mousemove"表示用户移动鼠标,"keydown"表示键盘上某个键被按下,而"load"表示文档(或某个其他资源)从网络上加载完毕。
  • 事件目标(event target)是发生的事件或与之相关的对象。当讲事件时,我们必须同时指明类型和目标。例如,window上的load事件或<button>元素的click事件。
  • 事件处理程序(event handler)或事件监听程序(event listener)是处理或响应事件的函数。当在特定的目标上发生特定类型的事件时,浏览器会调用对应的处理程序。
  • 事件对象(event object)是与特定事件相关且包含有关该事件详细信息的对象。事件对象作为参数传递给事件处理程序函数。所有的事件对象都有用来指定事件类型的type属性和指定事件目标的target属性
  • 事件传播(event propagation)是浏览器决定哪个对象触发其事件处理程序的过程。当文档元素上发生某个类型的事件时,它们会在文档树上向上传播或“冒泡”(bubble)。如果用户移动鼠标指针到超链接上,在定义这个链接的<a>元素上首先会触发mousemove事件,然后是在容器元素上触发这个事件,也许是<p>元素、<div>元素或Document对象本身。
  • 事件传播的另外一种形式称为事件捕获(event capturing),在容器元素上注册的特定处理程序有机会在事件传播到真实目标之前拦截(或“捕获”)它。
  • 事件处理程序可以通过返回一个适当的值、调用事件对象的某个方法或设置事件对象的某个属性来阻止默认操作的发生。例如,当超链接上发生click事件时,浏览器的默认操作是按照链接加载新页面

1.事件类型

1.1 传统事件类型

① 表单事件:

  • 当提交表单和重置表单时,<form>元素会分别触发submitreset事件。
  • 当用户和类按钮表单元素(包括单选按钮和复选框)交互时,它们会发生click事件。
  • 当用户通过输入文字、选择选项或选择复选框来改变相应表单元素的状态时,会触发change事件。对于文本输入域,只有用户和表单元素完成交互并通过Tab键或单击的方式移动焦点到其他元素上时才会触发change事件。
  • 响应通过键盘改变焦点的表单元素在得到和失去焦点时会分别触发focusblur事件。
  • 无论用户何时输入文字(通过键盘或剪切和粘贴)到<textarea>和其他文本输入表单元素,除IE外的浏览器都会触发input事件。

② Window事件:

Window事件是指事件的发生与浏览器窗口本身而非窗口中显示的任何特定文档内容相关

  • load事件是这些事件中最重要的一个,当文档和其所有外部资源(比如图片)完全加载并显示给用户时就会触发它。
  • unload事件和load相对,当用户离开当前文档转向其他文档时会触发它
  • Window对象的onerror属性有点像事件处理程序,当JavaScript出错时会触发它。
  • 像<img>元素这样的单个文档元素也能为load和error事件注册处理程序。
  • 当用户调整浏览器窗口大小或滚动它时会触发resizescroll事件。scroll事件也能在任何可以滚动的文档元素上触发

③ 鼠标事件

当用户在文档上移动或单击鼠标时都会产生鼠标事件。这些事件在鼠标指针所对应的最深嵌套元素上触发,但它们会冒泡直到文档最顶层。

  1. 传递给鼠标事件处理程序的事件对象有属性集,它们描述了当事件发生时鼠标的位置和按键状态,也指明当时是否有任何辅助键按下。
  2. clientX和clientY属性指定了鼠标在窗口坐标中的位置,
  3. button和which属性指定了按下的鼠标键是哪个。
  4. 当键盘辅助键按下时,对应的属性altkey、ctrlKey、metaKey和shiftKey会设置为true。
  5. 而对于click事件,detail属性指定了其是单击、双击还是三击。
  • 用户每次移动或拖动鼠标时,会触发mousemove事件。
  • 当用户按下或释放鼠标按键时,会触发mousedownmouseup事件。
  • 在mousedown和mouseup事件队列之后,浏览器也会触发click事件。
  • 如果用户在相当短的时间内连续两次单击鼠标按键,跟在第二个click事件之后是dblclick事件。
  • 当单击鼠标右键时,浏览器通常会显示上下文菜单(context menu)。在显示菜单之前,它们通常会触发contextmenu事件,而取消这个事件就可以阻止菜单的显示。这个事件也是获得鼠标右击通知的简单方法。
  • 当用户移动鼠标指针从而使它悬停到新元素上时,浏览器就会在该元素上触发mouseover事件。
  • 当鼠标移动指针从而使它不再悬停在某个元素上时,浏览器就会在该元素上触发mouseout事件。不冒泡版本mouseentermouseleave
<img onmouseover="bigImg(this)" onmouseout="normalImg(this)" border="0" src="/i/photo/smile.gif" alt="Smiley" width="128" height="128">
  • 当用户滚动鼠标滚轮时,浏览器触发wheel事件。传递的事件对象属性指定滚轮转动的大小和方向

④ 键盘事件

传递给键盘事件处理程序的事件对象有keyCode字段,它指定按下或释放的键是哪个。除了keyCode,键盘事件对象也有altKey、ctrlKey、metaKey和shiftKey,描述键盘辅助键的状态。

  • keydown和keyup事件是低级键盘事件,无论何时按下或释放按键(甚至是辅助键)都会触发它们。
  • 当keydown事件产生可打印字符时,在keydown和keyup之间会触发另外一个keypress事件。其事件对象指定产生的字符而非按下的键。

1.2 DOM事件

  • 3级DOM事件规范标准化了不冒泡的focusin和focusout事件来取代冒泡的focus和blur事件,标准化了冒泡的mouseenter和mouseleave事件来取代不冒泡的mouseover和mouseout事件
  • 3级DOM事件规范中新增内容有通过wheel事件对二维鼠标滚轮提供标准支持,通过textinput事件和传递新KeyboardEvent对象作为参数给keydown、keyup和keypress的事件处理程序来给文本输入事件提供更好的支持。

1.3 HTML5事件

  • HTML5特性之一的播放音频和视频的<audio>和<video>元素有长长的事件列表,它们触发各种关于网络事件、数据缓冲状况和播放状态的通知

image.png

  • HTML5定义了历史管理机制,它允许Web应用同浏览器的返回和前进按钮交互。这个机制涉及的事件是hashchangepopstate。这些事件是类似load和unload的生命周期通知事件,它在Window对象上触发而非任何单独的文档元素。
  • 当移动设备不在网络范围内时。相关的两个最重要事件是offlineonline,无论何时浏览器失去或得到网络连接都会在Window对象上触发它们。
  • 跨文档通信API允许一台服务器上的文档脚本能和另一台服务器上的文档脚本交换消息。其工作受限于同源策略。这一安全方式。发送的每一条消息都会在接收文档的Window上触发message事件。
  • XMLHttpRequest规范第2版和File API规范都定义了一系列事件来跟踪异步I/O的进度。它们在XMLHttpRequest或FileReader对象上触发事件。每次读取操作都是以loadstart事件开始,接着是progressloadend事件。每个操作仅在最终loadend事件之前会有load、error或abort事件
  • Window对象上发生的Web存储API定义了storage事件(在Window对象上)用于通知存储数据的改变
  • 当文档打印之前或之后立即在Window对象上触发引入的beforeprint和afterprint事件

2.注册事件处理程序

2.1 注册事件处理程序为JavaScript对象属性

//设置Window对象的unload属性为一个函数
//该函数是事件处理程序:当文档加载完毕时调用它
window.onload=function(){
  //查找一个<form>元素
  var elt=document.getElementById("shipping_address");//注册事件处理程序函数,
  //在表单提交之前调用它
  elt.onsubmit=function(){return validate(this);}
}

这种方法的缺点是每个事件目标对于每种事件类型将最多只有一个处理程序,更好的方式是使用一种不修改或覆盖任何已有注册处理程序的技术(比如addEventListener())

2.2 注册事件处理程序为HTML标签属性

<button onclick="alert('Thank you');">点击这里</button>

2.3 addEventListener()

任何能成为事件目标的对象都定义了可以为事件目标注册事件处理程序的addEventListener()方法。

addEventListener()接受三个参数。

第一个是要注册处理程序的事件类型,这个事件类型(或名字)是字符串,但它不应该包括用于设置事件处理程序属性的前缀"on"。

第二个参数是当指定类型的事件发生时应该调用的函数。

最后一个参数是布尔值。通常情况下,会给这个参数传递false。如果相反传递了true,那么函数将注册为捕获事件处理程序,并在事件不同的调度阶段调用。

var b=document.getElementById("mybutton");
b.onclick=function(){alert("Thanks for clicking me!");};
b.addEventListener("click",function(){alert("Thanks again!");},false);

在前面的代码中,单击按钮会产生两个alert()对话框。更重要的是,能通过多次调用addEventListener()为同一个对象注册同一事件类型的多个处理程序函数。

相对addEventListener()的是removeEventListener()方法,它同样有三个参数,从对象中删除事件处理程序函数

document.removeEventListener("mousemove",handleMouseMove,true);
document.removeEventListener("mouseup",handleMouseUp,true);

3 调用事件处理程序

一旦注册了事件处理程序,浏览器就会在指定对象上发生指定类型事件时自动调用它。

3.1 事件处理程序的参数

通常调用事件处理程序时把事件对象作为它们的一个参数。事件对象的属性提供了有关事件的详细信息

3.2 事件处理程序的this

当通过设置属性注册事件处理程序时,就好像是在文档元素上定义了新方法。事件处理程序在事件目标上定义,所以它们作为这个对象的方法来调用。在事件处理程序内,this关键字指的是事件目标。

同样当使用addEventListener()注册时,事件处理程序的this值指的也是事件目标。

3.3 事件处理程序的作用域

像所有的JavaScript函数一样,事件处理程序从词法上讲也是作用域。它们在其定义时的作用域而非调用时的作用域中执行,并且它们能存取那个作用域中的任何一个本地变量。

但是,通过HTML属性来注册事件处理程序是一个例外。它们被转换为能存取全局变量的顶级函数而非任何本地变量。例如,如果表单包含一个ID是"location"的元素,那么要是表单的所有HTML事件处理程序想引用window的location对象,就必须使用window.location而不能是location。

3.4 事件处理程序的返回值

通过设置对象属性或HTML属性注册事件处理程序的返回值有时是非常有意义的。通常情况下,返回值false就是告诉浏览器不要执行这个事件相关的默认操作。但只对通过属性注册的处理程序才有意义。

使用addEventListener()注册事件处理程序转而必须调用preventDefault()方法取消默认操作。

3.5 事件处理程序的调用顺序

  • 通过设置对象属性或HTML属性注册的处理程序一直优先调用。
  • 使用addEventListener()注册的处理程序按照它们的注册顺序调用

3.6 事件传播

在调用在目标元素上注册的事件处理函数后,大部分事件会“冒泡”到DOM树根。调用目标的父元素的事件处理程序,然后调用在目标的祖父元素上注册的事件处理程序。这会一直到Document对象,最后到达Window对象。

发生在文档元素上的大部分事件都会冒泡,但例外的是focus、blur和scroll事件。文档元素上的load事件会冒泡,但它会在Document对象上停止冒泡而不会传播到Window对象。

事件冒泡是事件传播的第三个“阶段”。目标对象本身的事件处理程序调用是第二个阶段。第一个阶段甚至发生在目标处理程序调用之前,称为“捕获”阶段

3.7 事件取消

事件取消: 取消事件相关的默认操作或者取消事件传播

  • 在支持addEventListener()的浏览器中,能通过调用事件对象的preventDefault()方法取消事件的默认操作。
  • 在支持addEventListener()的浏览器中,可以调用事件对象的一个stopPropagation()方法以阻止事件的继续传播

如果在同一对象上定义了其他处理程序,剩下的处理程序将依旧被调用,但调用stopPropagation()之后任何其他对象上的事件处理程序将不会被调用。stopPropagation()方法可以在事件传播期间的任何时间调用,它能工作在捕获期阶段、事件目标本身中和冒泡阶段。

4 文档加载事件

  • load事件直到文档和所有图片加载完毕时才发生。
  • 当文档加载解析完毕且所有延迟(deferred)脚本都执行完毕时会触发DOMContentLoaded事件,此时图片和异步(async)脚本可能依旧在加载,但是文档已经为操作准备就绪了。 document.readyState属性随着文档加载过程而变。

5 鼠标事件

image.png 除"mouseenter"和"mouseleave"外的所有鼠标事件都能冒泡。

  • 传递给鼠标事件处理程序的事件对象有clientX和clientY属性,它们指定了鼠标指针相对于包含窗口的坐标。加入窗口的滚动偏移量就可以把鼠标位置转换成文档坐标。
  • altKey、ctrlKey、metaKey和shiftKey属性指定了当事件发生时是否有各种键盘辅助键按下。例如,这让你能够区分普通单击和按着Shift键的单击。

鼠标拖放事件

image.png

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>事件处理</title>
    <style>
      .droptarget {
        float: left;
        width: 100px;
        height: 35px;
        margin: 15px;
        margin-right: 100px;
        padding: 10px;
        border: 1px solid #aaaaaa;
      }
    </style>
  </head>
  <body>
    <p>在两个矩形框中来回拖动 p 元素:</p>
    <div
      class="droptarget"
      ondrop="drop(event)"
      ondragenter="dragEnter(event)"
      ondragleave="dragLeave(event)"
      ondragover="allowDrop(event)"
    >
      <p ondragstart="dragStart(event)" draggable="true" id="dragtarget">
        拖动我!
      </p>
    </div>
    <div
      class="droptarget"
      ondragenter="dragEnter(event)"
      ondragleave="dragLeave(event)"
      ondrop="drop(event)"
      ondragover="allowDrop(event)"
    ></div>
    <p style="clear:both;">
      <strong>注意:</strong>Internet Explorer 8 及更早 IE 版本或 Safari 5.1
      及更早版本的浏览器不支持 drag 事件。
    </p>
    <p id="demo"></p>
    <script>
      function dragStart(event) {
        event.dataTransfer.setData("Text", event.target.id);
      }
      function dragEnter(event) {
        document.getElementById("demo").innerHTML = "进入放置区域";
        if (event.target.className == "droptarget") {
          event.target.style.border = "3px dotted red";
        }
      }
      function dragLeave(event) {
        document.getElementById("demo").innerHTML = "离开放置区域";
        if (event.target.className == "droptarget") {
          event.target.style.border = "";
        }
      }
      function allowDrop(event) {
        event.preventDefault();
      }
      function drop(event) {
        event.preventDefault();
        var data = event.dataTransfer.getData("Text");
        event.target.appendChild(document.getElementById(data));
      }
    </script>
  </body>
</html>

6 鼠标滚轮事件

3级DOM事件规范草案标准定义了wheel事件作为mousewheel和DOMMouseScroll的标准版本。传递给wheel事件处理程序的事件对象将有deltaX、deltaY和deltaZ属性,以指定三个维度的旋转。

<div id="myDIV">
  在这个区域上下滚动鼠标滚轮! div 的字体大小变为 35 
</div>
<p><strong>注意:</strong> Safari 浏览器不支持 wheel 事件</p>
<script>
  document.getElementById("myDIV").addEventListener("wheel", myFunction);
  function myFunction() {
    document.getElementById("myDIV").style.fontSize = "35px";
  }
</script>

7. 文本事件

  • 3级DOM事件规范草案定义一个更通用的textinput事件,不管来源(例如:键盘、粘贴或拖放形式的数据传输、亚洲语言输入法、声音或手写识别系统),无论何时用户输入文本时都会触发它。
  • 一个keypress事件表示输入的单个字符。事件对象以数字Unicode编码的形式指定字符,所以必须用String.fromCharCode()把它转换成字符串。

image.png

  • keypress和textinput事件是在新输入的文本真正插入到聚焦的文档元素前触发,这就是这些事件处理程序能够取消事件和阻止文本插入的原因。浏览器也实现了在文本插入到元素后才触发的input事件。
<input type="text" oninput="this.value=this.value.toUpperCase();"
  • 可以使用不标准的propertychange事件检测文本输入元素的value属性改变来实现和input相似的效果

8 键盘事件

当用户在键盘上按下或释放按键时,会发生keydown和keyup事件。它们由辅助键、功能键和字母数字键产生。 如果用户按键时间足够长会导致它开始重复,那么在keyup事件到达之前会收到多个keydown事件。

  • 事件对象都有数字属性keyCode,指定了按下的键是哪个。
  • 类似鼠标事件对象,键盘事件对象有altKey、ctrlKey、metakey和shiftKey属性,当事件发生时,如果对应的辅助键被按下,那么它们会被设置为true。
  • 3级DOM事件规范定义了新属性key,它会以字符串的形式包含键名 辅助键(modifier key)一般是指Shift键、Ctrl(control)键、Alt键、AltGr(Alternate Graphic)键、Super键(Window键盘上指Window键,Mac OS键盘指的是Command键,Sun键盘指的是Meta键)和Fn键(Function,常见于笔记本键盘); 功能键(function key)一般是指类似F1、F2这些以F加数字组成的键