当前位置: 首页 > news >正文

工艺品网站模版最新发布的最新

工艺品网站模版,最新发布的最新,网站建设juxinghulian,wordpress添加广告位十七、事件 事件 JavaScript 与 HTML 的交互是通过事件实现的#xff0c;事件代表文档或浏览器窗口中某个有意义的时刻。可以使用仅在事件发生时执行的监听器#xff08;也叫处理程序#xff09;订阅事件。在传统软件工程领域#xff0c;这个模型叫“观察者模式”#xff…十七、事件 事件 JavaScript 与 HTML 的交互是通过事件实现的事件代表文档或浏览器窗口中某个有意义的时刻。可以使用仅在事件发生时执行的监听器也叫处理程序订阅事件。在传统软件工程领域这个模型叫“观察者模式”其能够做到页面行为在 JavaScript 中定义与页面展示在 HTML 和 CSS 中定义的分离。事件最早是在 IE3 和 Netscape Navigator 2 中出现的当时的用意是把某些表单处理工作从服务器转移到浏览器上来。到了 IE4 和 Netscape Navigator 3 发布的时候这两家浏览器都提供了类似但又不同的API而且持续了好几代。DOM2 开始尝试以符合逻辑的方式来标准化 DOM 事件 API。目前所有现代浏览器都实现了 DOM2 Events 的核心部分。IE8 是最后一个使用专有事件系统的主流浏览器。浏览器的事件系统非常复杂。即使所有主流浏览器都实现了 DOM2 Events规范也没有涵盖所有的事件类型。BOM 也支持事件这些事件与 DOM 事件之间的关系由于长期以来缺乏文档经常容易被混淆HTML5 已经致力于明确这些关系。而 DOM3 新增的事件 API 又让这些问题进一步复杂化了。根据具体的需求不同使用事件可能会相对简单也可能会非常复杂。但无论如何理解其中的核心概念还是最重要的。 事件流 在第四代 Web 浏览器IE4 和 Netscape Communicator 4开始开发时开发团队碰到了一个有意思的问题页面哪个部分拥有特定的事件呢要理解这个问题可以在一张纸上画几个同心圆。把手指放到圆心上则手指不仅是在一个圆圈里而且是在所有的圆圈里。两家浏览器的开发团队都是以同样的方式看待浏览器事件的。当你点击一个按钮时实际上不光点击了这个按钮还点击了它的容器以及整个页面。事件流描述了页面接收事件的顺序。结果非常有意思IE 和 Netscape 开发团队提出了几乎完全相反的事件流方案。IE 将支持事件冒泡流而 Netscape Communicator 将支持事件捕获流。 事件冒泡 IE 事件流被称为事件冒泡这是因为事件被定义为从最具体的元素文档树中最深的节点开始触发然后向上传播至没有那么具体的元素文档。比如有如下 HTML 页面!DOCTYPE html html head titleEvent Bubbling Example/title /head body div idmyDivClick Me/div /body /html在点击页面中的div元素后click 事件会以如下顺序发生 (1) div(2) body(3) html(4) document 也就是说div元素即被点击的元素最先触发 click 事件。然后click 事件沿 DOM 树一路向上在经过的每个节点上依次触发直至到达 document 对象。 所有现代浏览器都支持事件冒泡只是在实现方式上会有一些变化。IE5.5及早期版本会跳过html元素从body直接到 document。现代浏览器中的事件会一直冒泡到 window 对象。 事件捕获 Netscape Communicator 团队提出了另一种名为事件捕获的事件流。事件捕获的意思是最不具体的节点应该最先收到事件而最具体的节点应该最后收到事件。事件捕获实际上是为了在事件到达最终目标前拦截事件。如果前面的例子使用事件捕获则点击div元素会以下列顺序触发 click 事件 (1) document(2) html(3) body(4) div 在事件捕获中click 事件首先由 document 元素捕获然后沿 DOM 树依次向下传播直至到达实际的目标元素div。虽然这是 Netscape Communicator 唯一的事件流模型但事件捕获得到了所有现代浏览器的支持。实际上所有浏览器都是从 window 对象开始捕获事件而 DOM2 Events规范规定的是从 document 开始。由于旧版本浏览器不支持因此实际当中几乎不会使用事件捕获。通常建议使用事件冒泡特殊情况下可以使用事件捕获。 DOM 事件流 DOM2 Events 规范规定事件流分为 3 个阶段事件捕获、到达目标和事件冒泡。事件捕获最先发生为提前拦截事件提供了可能。然后实际的目标元素接收到事件。最后一个阶段是冒泡最迟要在这个阶段响应事件。在 DOM 事件流中实际的目标div元素在捕获阶段不会接收到事件。这是因为捕获阶段从document 到html再到body就结束了。下一阶段即会在div元素上触发事件的“到达目标”阶段通常在事件处理时被认为是冒泡阶段的一部分稍后讨论。然后冒泡阶段开始事件反向传播至文档。大多数支持 DOM 事件流的浏览器实现了一个小小的拓展。虽然 DOM2 Events 规范明确捕获阶段不命中事件目标但现代浏览器都会在捕获阶段在事件目标上触发事件。最终结果是在事件目标上有两个机会来处理事件。注意所有现代浏览器都支持 DOM 事件流只有 IE8 及更早版本不支持。 事件处理程序。事件意味着用户或浏览器执行的某种动作。比如单击click、加载load、鼠标悬停mouseover。为响应事件而调用的函数被称为事件处理程序或事件监听器。事件处理程序的名字以on开头因此 click 事件的处理程序叫作 onclick而 load 事件的处理程序叫作 onload。有很多方式可以指定事件处理程序。 HTML 事件处理程序 特定元素支持的每个事件都可以使用事件处理程序的名字以 HTML 属性的形式来指定。此时属性的值必须是能够执行的 JavaScript 代码。例如要在按钮被点击时执行某些 JavaScript 代码可以使用以下 HTML 属性input typebutton valueClick Me onclickconsole.log(Clicked)/点击这个按钮后控制台会输出一条消息。这种交互能力是通过为 onclick 属性指定 JavaScript代码值来实现的。注意因为属性的值是 JavaScript 代码所以不能在未经转义的情况下使用 HTML 语法字符比如和号、双引号、小于号和大于号。此时为了避免使用 HTML 实体可以使用单引号代替双引号。如果确实需要使用双引号则要把代码改成下面这样input typebutton valueClick Me onclickconsole.log(quot;Clickedquot;)/在 HTML 中定义的事件处理程序可以包含精确的动作指令也可以调用在页面其他地方定义的脚本比如script function showMessage() { console.log(Hello world!); } /script input typebutton valueClick Me onclickshowMessage()/在这个例子中单击按钮会调用 showMessage()函数。showMessage()函数是在单独的script元素中定义的而且也可以在外部文件中定义。作为事件处理程序执行的代码可以访问全局作用域中的一切。 以这种方式指定的事件处理程序有一些特殊的地方。首先会创建一个函数来封装属性的值。这个函数有一个特殊的局部变量 event其中保存的就是 event 对象本章后面会讨论!-- 输出click -- input typebutton valueClick Me onclickconsole.log(event.type)有了这个对象就不用开发者另外定义其他变量也不用从包装函数的参数列表中去取了。在这个函数中this 值相当于事件的目标元素如下面的例子所示!-- 输出Click Me -- input typebutton valueClick Me onclickconsole.log(this.value)这个动态创建的包装函数还有一个特别有意思的地方就是其作用域链被扩展了。在这个函数中document 和元素自身的成员都可以被当成局部变量来访问。这是通过使用 with 实现的function() { with(document) { with(this) { // 属性值} } }这意味着事件处理程序可以更方便地访问自己的属性。下面的代码与前面的示例功能一样!-- 输出Click Me -- input typebutton valueClick Me onclickconsole.log(value)如果这个元素是一个表单输入框则作用域链中还会包含表单元素事件处理程序对应的函数等价于如下这样function() { with(document) { with(this.form) {with(this) { // 属性值} } } }本质上经过这样的扩展事件处理程序的代码就可以不必引用表单元素而直接访问同一表单中的其他成员了。下面的例子就展示了这种成员访问模式form methodpost input typetext nameusername value input typebutton valueEcho Username onclickconsole.log(username.value) /form点击这个例子中的按钮会显示出文本框中包含的文本。注意事件处理程序中的代码直接引用了username。在 HTML 中指定事件处理程序有一些问题。第一个问题是时机问题。有可能 HTML 元素已经显示在页面上用户都与其交互了而事件处理程序的代码还无法执行。比如在前面的例子中如果showMessage()函数是在页面后面在按钮中代码的后面定义的那么当用户在 showMessage()函数被定义之前点击按钮时就会发生错误。为此大多数 HTML 事件处理程序会封装在 try/catch 块中以便在这种情况下静默失败如下面的例子所示input typebutton valueClick Me onclicktry{showMessage();}catch(ex) {}这样如果在 showMessage()函数被定义之前点击了按钮就不会发生 JavaScript 错误了这是因为错误在浏览器收到之前已经被拦截了。 另一个问题是对事件处理程序作用域链的扩展在不同浏览器中可能导致不同的结果。不同JavaScript 引擎中标识符解析的规则存在差异因此访问无限定的对象成员可能导致错误。使用 HTML 指定事件处理程序的最后一个问题是 HTML 与 JavaScript 强耦合。如果需要修改事件处理程序则必须在两个地方即 HTML 和 JavaScript 中修改代码。这也是很多开发者不使用 HTML事件处理程序而使用 JavaScript 指定事件处理程序的主要原因。 DOM0 事件处理程序 在 JavaScript 中指定事件处理程序的传统方式是把一个函数赋值给DOM 元素的一个事件处理程序属性。这也是在第四代 Web 浏览器中开始支持的事件处理程序赋值方法直到现在所有现代浏览器仍然都支持此方法主要原因是简单。要使用 JavaScript 指定事件处理程序必须先取得要操作对象的引用。每个元素包括 window 和 document都有通常小写的事件处理程序属性比如 onclick。只要把这个属性赋值为一个函数即可let btn document.getElementById(myBtn); btn.onclick function() { console.log(Clicked); };这里先从文档中取得按钮然后给它的 onclick 事件处理程序赋值一个函数。注意前面的代码在运行之后才会给事件处理程序赋值。因此如果在页面中上面的代码出现在按钮之后则有可能出现用户点击按钮没有反应的情况。像这样使用 DOM0 方式为事件处理程序赋值时所赋函数被视为元素的方法。因此事件处理程序会在元素的作用域中运行即 this 等于元素。下面的例子演示了使用 this 引用元素本身let btn document.getElementById(myBtn); btn.onclick function() { console.log(this.id); // myBtn };点击按钮这段代码会显示元素的 ID。这个 ID 是通过 this.id 获取的。不仅仅是 id在事件处理程序里通过 this 可以访问元素的任何属性和方法。以这种方式添加事件处理程序是注册在事件流的冒泡阶段的。通过将事件处理程序属性的值设置为 null可以移除通过 DOM0 方式添加的事件处理程序如下面的例子所示btn.onclick null; // 移除事件处理程序把事件处理程序设置为 null再点击按钮就不会执行任何操作了。注意如果事件处理程序是在 HTML 中指定的则 onclick 属性的值是一个包装相应HTML 事件处理程序属性值的函数。这些事件处理程序也可以通过在 JavaScript 中将相应属性设置为 null 来移除。 DOM2 事件处理程序 DOM2 Events 为事件处理程序的赋值和移除定义了两个方法addEventListener()和 removeEventListener()。这两个方法暴露在所有 DOM 节点上它们接收 3 个参数事件名、事件处理函数和一个布尔值true 表示在捕获阶段调用事件处理程序false默认值表示在冒泡阶段调用事件处理程序。仍以给按钮添加 click 事件处理程序为例可以这样写let btn document.getElementById(myBtn); btn.addEventListener(click, () { console.log(this.id); }, false);以上代码为按钮添加了会在事件冒泡阶段触发的 onclick 事件处理程序因为最后一个参数值为false。与 DOM0 方式类似这个事件处理程序同样在被附加到的元素的作用域中运行。使用 DOM2方式的主要优势是可以为同一个事件添加多个事件处理程序。来看下面的例子let btn document.getElementById(myBtn); btn.addEventListener(click, () { console.log(this.id); }, false); btn.addEventListener(click, () { console.log(Hello world!); }, false);这里给按钮添加了两个事件处理程序。多个事件处理程序以添加顺序来触发因此前面的代码会先打印元素 ID然后显示消息“Hello world!”。通过 addEventListener()添加的事件处理程序只能使用 removeEventListener()并传入与添加时同样的参数来移除。这意味着使用 addEventListener()添加的匿名函数无法移除如下面的例子所示let btn document.getElementById(myBtn); btn.addEventListener(click, () { console.log(this.id); }, false); // 其他代码 btn.removeEventListener(click, function() { // 没有效果console.log(this.id); }, false);这个例子通过 addEventListener()添加了一个匿名函数作为事件处理程序。然后又以看起来相同的参数调用了 removeEventListener()。但实际上第二个参数与传给 addEventListener()的完全不是一回事。传给 removeEventListener()的事件处理函数必须与传给 addEventListener()的是同一个如下面的例子所示let btn document.getElementById(myBtn); let handler function() { console.log(this.id); }; btn.addEventListener(click, handler, false); // 其他代码 btn.removeEventListener(click, handler, false); // 有效果这个例子有效因为调用 addEventListener()和 removeEventListener()时传入的是同一个函数。大多数情况下事件处理程序会被添加到事件流的冒泡阶段主要原因是跨浏览器兼容性好。把事件处理程序注册到捕获阶段通常用于在事件到达其指定目标之前拦截事件。如果不需要拦截则不要使用事件捕获。 IE 事件处理程序 IE 实现了与 DOM 类似的方法即 attachEvent()和 detachEvent()。这两个方法接收两个同样的参数事件处理程序的名字和事件处理函数。因为 IE8 及更早版本只支持事件冒泡所以使用attachEvent()添加的事件处理程序会添加到冒泡阶段。要使用 attachEvent()给按钮添加 click 事件处理程序可以使用以下代码var btn document.getElementById(myBtn); btn.attachEvent(onclick, function() { console.log(Clicked); });注意attachEvent()的第一个参数是onclick而不是 DOM 的 addEventListener()方法的click。 在 IE 中使用 attachEvent()与使用 DOM0 方式的主要区别是事件处理程序的作用域。使用 DOM0方式时事件处理程序中的 this 值等于目标元素。而使用 attachEvent()时事件处理程序是在全局作用域中运行的因此 this 等于 window。来看下面使用 attachEvent()的例子var btn document.getElementById(myBtn); btn.attachEvent(onclick, function() { console.log(this window); // true });理解这些差异对编写跨浏览器代码是非常重要的。与使用 addEventListener()一样使用 attachEvent()方法也可以给一个元素添加多个事件处理程序。比如下面的例子var btn document.getElementById(myBtn); btn.attachEvent(onclick, function() { console.log(Clicked); }); btn.attachEvent(onclick, function() { console.log(Hello world!); });这里调用了两次 attachEvent()分别给同一个按钮添加了两个不同的事件处理程序。不过**与DOM 方法不同这里的事件处理程序会以添加它们的顺序反向触发。**换句话说在点击例子中的按钮后控制台中会先打印出Hello world!“然后再打印出Clicked”。使用 attachEvent()添加的事件处理程序将使用 detachEvent()来移除只要提供相同的参数。与使用 DOM 方法类似作为事件处理程序添加的匿名函数也无法移除。但只要传给 detachEvent()方法相同的函数引用就可以移除。下面的例子演示了附加和剥离事件var btn document.getElementById(myBtn); var handler function() { console.log(Clicked); }; btn.attachEvent(onclick, handler); // 其他代码 btn.detachEvent(onclick, handler);这里先把事件处理程序保存到变量 handler之后又将其传给 detachEvent()来移除事件处理程序。 跨浏览器事件处理程序 为了以跨浏览器兼容的方式处理事件很多开发者会选择使用一个 JavaScript 库其中抽象了不同浏览器的差异。有些开发者也可能会自己编写代码以便使用最合适的事件处理手段。自己编写跨浏览器事件处理代码也很简单主要依赖能力检测。要确保事件处理代码具有最大兼容性只需要让代码在冒泡阶段运行即可。为此需要先创建一个 addHandler()方法。这个方法的任务是根据需要分别使用 DOM0 方式、DOM2 方式或 IE 方式来添加事件处理程序。这个方法会在 EventUtil 对象本章示例使用的对象上添加一个方法以实现跨浏览器事件处理。添加的这个 addHandler()方法接收 3 个参数目标元素、事件名和事件处理函数。有了 addHandler()还要写一个也接收同样的 3 个参数的 removeHandler()。这个方法的任务是移除之前添加的事件处理程序不管是通过何种方式添加的默认为 DOM0 方式。以下就是包含这两个方法的 EventUtil 对象var EventUtil { addHandler: function(element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { element.attachEvent(on type, handler); } else { element[on type] handler; } }, removeHandler: function(element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else if (element.detachEvent) { element.detachEvent(on type, handler); } else { element[on type] null; } } };两个方法都是首先检测传入元素上是否存在 DOM2 方式。如果有 DOM2 方式就使用该方式传入事件类型和事件处理函数以及表示冒泡阶段的第三个参数 false。否则如果存在 IE 方式则使用该方式。注意这时候必须在事件类型前加上on才能保证在 IE8 及更早版本中有效。最后是使用DOM0 方式在现代浏览器中不会到这一步。注意使用 DOM0 方式时使用了中括号计算属性名并将事件处理程序或 null 赋给了这个属性。可以像下面这样使用 EventUtil 对象let btn document.getElementById(myBtn) let handler function() { console.log(Clicked); }; EventUtil.addHandler(btn, click, handler); // 其他代码 EventUtil.removeHandler(btn, click, handler);这里的 addHandler()和 removeHandler()方法并没有解决所有跨浏览器一致性问题比如 IE的作用域问题、多个事件处理程序执行顺序问题等。不过这两个方法已经实现了跨浏览器添加和移除事件处理程序。另外也要注意**DOM0 只支持给一个事件添加一个处理程序。**好在 DOM0 浏览器已经很少有人使用了所以影响应该不大。 事件对象。在 DOM 中发生事件时所有相关信息都会被收集并存储在一个名为 event 的对象中。这个对象包含了一些基本信息比如导致事件的元素、发生的事件类型以及可能与特定事件相关的任何其他数据。例如鼠标操作导致的事件会生成鼠标位置信息而键盘操作导致的事件会生成与被按下的键有关的信息。所有浏览器都支持这个 event 对象尽管支持方式不同。 DOM 事件对象 在 DOM 合规的浏览器中event 对象是传给事件处理程序的唯一参数。不管以哪种方式DOM0或 DOM2指定事件处理程序都会传入这个 event 对象。下面的例子展示了在两种方式下都可以使用事件对象 let btn document.getElementById(myBtn); btn.onclick function(event) { console.log(event.type); // click }; btn.addEventListener(click, (event) { console.log(event.type); // click }, false);这个例子中的两个事件处理程序都会在控制台打出 event.type 属性包含的事件类型。这个属性中始终包含被触发事件的类型如click与传给 addEventListener()和 removeEventListener()方法的事件名一致。 在通过 HTML 属性指定的事件处理程序中同样可以使用变量 event 引用事件对象。下面的例子中演示了如何使用这个变量 input typebutton valueClick Me onclickconsole.log(event.type)以这种方式提供 event 对象可以让 HTML 属性中的代码实现与 JavaScript 函数同样的功能。 如前所述事件对象包含与特定事件相关的属性和方法。不同的事件生成的事件对象也会包含不同的属性和方法。不过所有事件对象都会包含下表列出的这些公共属性和方法。 属性/方法类型读/写说明bubbles布尔值只读表示事件是否冒泡cancelable布尔值只读表示是否可以取消事件的默认行为currentTarget元素只读当前事件处理程序所在的元素defaultPrevented布尔值只读true 表示已经调用 preventDefault()方法DOM3 Events 中新增detail整数只读事件相关的其他信息eventPhase整数只读表示调用事件处理程序的阶段1 代表捕获阶段2 代表到达目标3 代表冒泡阶段preventDefault()函数只读用于取消事件的默认行为。只有 cancelable 为 true 才可以调用这个方法stopImmediatePropagation()函数只读用于取消所有后续事件捕获或事件冒泡并阻止调用任何后续事件处理程序DOM3 Events 中新增stopPropagation()函数只读用于取消所有后续事件捕获或事件冒泡。只有 bubbles为 true 才可以调用这个方法target元素只读事件目标trusted布尔值只读true 表示事件是由浏览器生成的。false 表示事件是开发者通过 JavaScript 创建的DOM3 Events 中新增type字符串只读被触发的事件类型ViewAbstractView只读与事件相关的抽象视图。等于事件所发生的 window 对象 在事件处理程序内部this 对象始终等于 currentTarget 的值而 target 只包含事件的实际目标。如果事件处理程序直接添加在了意图的目标则 this、currentTarget 和 target 的值是一样的。下面的例子展示了这两个属性都等于 this 的情形 let btn document.getElementById(myBtn); btn.onclick function(event) { console.log(event.currentTarget this); // true console.log(event.target this); // true };上面的代码检测了 currentTarget 和 target 的值是否等于 this。因为 click 事件的目标是按钮所以这 3 个值是相等的。如果这个事件处理程序是添加到按钮的父节点如 document.body上那么它们的值就不一样了。比如下面的例子在 document.body 上添加了单击处理程序 document.body.onclick function(event) { console.log(event.currentTarget document.body); // true console.log(this document.body); // true console.log(event.target document.getElementById(myBtn)); // true };这种情况下点击按钮this 和 currentTarget 都等于 document.body这是因为它是注册事件处理程序的元素。而 target 属性等于按钮本身这是因为那才是 click 事件真正的目标。由于按钮本身并没有注册事件处理程序因此 click 事件冒泡到 document.body从而触发了在它上面注册的处理程序。 type 属性在一个处理程序处理多个事件时很有用。比如下面的处理程序中就使用了 event.type let btn document.getElementById(myBtn); let handler function(event) { switch(event.type) { case click: console.log(Clicked); break; case mouseover:event.target.style.backgroundColor red; break; case mouseout: event.target.style.backgroundColor ; break; } }; btn.onclick handler; btn.onmouseover handler; btn.onmouseout handler;在这个例子中函数 handler 被用于处理 3 种不同的事件click、mouseover 和 mouseout。当按钮被点击时应该在控制台打印一条消息如前面的例子所示。而把鼠标放到按钮上会导致按钮背景变成红色接着把鼠标从按钮上移开背景颜色应该又恢复成默认值。这个函数使用 event.type属性确定了事件类型从而可以做出不同的响应。 preventDefault()方法用于阻止特定事件的默认动作。比如链接的默认行为就是在被单击时导航到 href 属性指定的 URL。如果想阻止这个导航行为可以在 onclick 事件处理程序中取消如下面的例子所示 let link document.getElementById(myLink); link.onclick function(event) { event.preventDefault(); };任何可以通过 preventDefault()取消默认行为的事件其事件对象的 cancelable 属性都会设置为 true。 stopPropagation()方法用于立即阻止事件流在 DOM 结构中传播取消后续的事件捕获或冒泡。例如直接添加到按钮的事件处理程序中调用 stopPropagation()可以阻止 document.body 上注册的事件处理程序执行。比如 let btn document.getElementById(myBtn); btn.onclick function(event) { console.log(Clicked); event.stopPropagation(); }; document.body.onclick function(event) { console.log(Body clicked); };如果这个例子中不调用stopPropagation()那么点击按钮就会打印两条消息。但这里由于click事件不会传播到 document.body因此 onclick 事件处理程序永远不会执行。 eventPhase 属性可用于确定事件流当前所处的阶段。如果事件处理程序在捕获阶段被调用则eventPhase 等于 1如果事件处理程序在目标上被调用则 eventPhase 等于 2如果事件处理程序在冒泡阶段被调用则 eventPhase 等于 3。不过要注意的是虽然“到达目标”是在冒泡阶段发生的但其 eventPhase 仍然等于 2。下面的例子展示了 eventPhase 在不同阶段的值 let btn document.getElementById(myBtn); btn.onclick function(event) { console.log(event.eventPhase); // 2 }; document.body.addEventListener(click, (event) { console.log(event.eventPhase); // 1 }, true); document.body.onclick (event) { console.log(event.eventPhase); // 3 };在这个例子中点击按钮首先会触发注册在捕获阶段的 document.body 上的事件处理程序显示 eventPhase 为 1。接着会触发按钮本身的事件处理程序尽管是注册在冒泡阶段此时显示 eventPhase 等于 2。最后触发的是注册在冒泡阶段的 document.body 上的事件处理程序显示eventPhase 为 3。而当 eventPhase 等于 2 时this、target 和 currentTarget 三者相等。 注意event 对象只在事件处理程序执行期间存在一旦执行完毕就会被销毁。 IE 事件对象 与 DOM 事件对象不同 IE 事件对象可以基于事件处理程序被指定的方式以不同方式来访问。如果事件处理程序是使用 DOM0 方式指定的则 event 对象只是 window 对象的一个属性如下所示 var btn document.getElementById(myBtn); btn.onclick function() { let event window.event; console.log(event.type); // click };这里window.event 中保存着 event 对象其 event.type 属性保存着事件类型IE 的这个属性的值与 DOM 事件对象中一样。不过如果事件处理程序是使用 attachEvent()指定的则 event对象会作为唯一的参数传给处理函数如下所示 var btn document.getElementById(myBtn); btn.attachEvent(onclick, function(event) { console.log(event.type); // click });使用 attachEvent()时event 对象仍然是 window 对象的属性像 DOM0 方式那样只是出于方便也将其作为参数传入。 如果是使用 HTML 属性方式指定的事件处理程序则 event 对象同样可以通过变量 event 访问与DOM 模型一样。下面是在 HTML 事件属性中使用 event.type 的例子 input typebutton valueClick Me onclickconsole.log(event.type) IE 事件对象也包含与导致其创建的特定事件相关的属性和方法其中很多都与相关的 DOM 属性和方法对应。与 DOM 事件对象一样基于触发的事件类型不同event 对象中包含的属性和方法也不一样。不过所有 IE 事件对象都会包含下表所列的公共属性和方法。 属性/方法类型读/写说明cancelBubble布尔值读/写默认为 false设置为 true 可以取消冒泡与 DOM 的stopPropagation()方法相同returnValue布尔值读/写默认为 true设置为 false 可以取消事件默认行为与 DOM 的 preventDefault()方法相同srcElement元素只读事件目标与 DOM 的 target 属性相同type字符串只读触发的事件类型 由于事件处理程序的作用域取决于指定它的方式因此 this 值并不总是等于事件目标。为此更好的方式是使用事件对象的 srcElement 属性代替 this。下面的例子表明不同事件对象上的srcElement 属性中保存的都是事件目标 var btn document.getElementById(myBtn); btn.onclick function() { console.log(window.event.srcElement this); // true }; btn.attachEvent(onclick, function(event) { console.log(event.srcElement this); // false });在第一个以 DOM0 方式指定的事件处理程序中srcElement 属性等于 this而在第二个事件处理程序中运行在全局作用域下两个值就不相等了。 returnValue 属性等价于 DOM 的 preventDefault()方法都是用于取消给定事件默认的行为。只不过在这里要把 returnValue 设置为 false 才是阻止默认动作。下面是一个设置该属性的例子 var link document.getElementById(myLink); link.onclick function() { window.event.returnValue false; };在这个例子中returnValue 在 onclick 事件处理程序中被设置为 false阻止了链接的默认行为。与 DOM 不同没有办法通过 JavaScript 确定事件是否可以被取消。 cancelBubble 属性与 DOMstopPropagation()方法用途一样都可以阻止事件冒泡。因为 IE8及更早版本不支持捕获阶段所以只会取消冒泡。stopPropagation()则既取消捕获也取消冒泡。下面是一个取消冒泡的例子 var btn document.getElementById(myBtn); btn.onclick function() { console.log(Clicked); window.event.cancelBubble true; }; document.body.onclick function() { console.log(Body clicked); };通过在按钮的 onclick 事件处理程序中将 cancelBubble 设置为 true可以阻止事件冒泡到document.body也就阻止了调用注册在它上面的事件处理程序。于是点击按钮只会输出一条消息。 跨浏览器事件对象 虽然 DOM 和 IE 的事件对象并不相同但它们有足够的相似性可以实现跨浏览器方案。DOM 事件对象中包含 IE 事件对象的所有信息和能力只是形式不同。这些共性可让两种事件模型之间的映射成为可能。本章前面的 EventUtil 对象可以像下面这样再添加一些方法var EventUtil { addHandler: function(element, type, handler) { // 为节省版面删除了之前的代码}, getEvent: function(event) { return event ? event : window.event; }, getTarget: function(event) { return event.target || event.srcElement; }, preventDefault: function(event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue false; } }, removeHandler: function(element, type, handler) { // 为节省版面删除了之前的代码}, stopPropagation: function(event) { if (event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble true; } } };这里一共给 EventUtil 增加了 4 个新方法。首先是 getEvent()其返回对 event 对象的引用。IE 中事件对象的位置不同而使用这个方法可以不用管事件处理程序是如何指定的都可以获取到event 对象。使用这个方法的前提是事件处理程序必须接收 event 对象并把它传给这个方法。下面是使用 EventUtil 中这个方法统一获取 event 对象的一个例子btn.onclick function(event) { event EventUtil.getEvent(event); };在 DOM 合规的浏览器中event 对象会直接传入并返回。而在 IE 中event 对象可能并没有被定义因为使用了 attachEvent()因此返回 window.event。这样就可以确保无论使用什么浏览器都可以获取到事件对象。第二个方法是 getTarget()其返回事件目标。在这个方法中首先检测 event 对象是否存在target 属性。如果存在就返回这个值否则就返回 event.srcElement 属性。下面是使用这个方法的示例btn.onclick function(event) { event EventUtil.getEvent(event); let target EventUtil.getTarget(event); };第三个方法是 preventDefault()其用于阻止事件的默认行为。在传入的 event 对象上如果有 preventDefault()方法就调用这个方法否则就将 event.returnValue 设置为 false。下面是使用这个方法的例子let link document.getElementById(myLink); link.onclick function(event) { event EventUtil.getEvent(event); EventUtil.preventDefault(event); };以上代码能在所有主流浏览器中阻止单击链接后跳转到其他页面。这里首先通过 EventUtil.getEvent()获取事件对象然后又把它传给了 EventUtil.preventDefault()以阻止默认行为。 第四个方法 stopPropagation()以类似的方式运行。同样先检测用于停止事件流的 DOM 方法如果没有再使用 cancelBubble 属性。下面是使用这个通用 stopPropagation()方法的示例let btn document.getElementById(myBtn); btn.onclick function(event) { console.log(Clicked); event EventUtil.getEvent(event); EventUtil.stopPropagation(event); }; document.body.onclick function(event) { console.log(Body clicked); };同样先通过 EventUtil.getEvent()获取事件对象然后又把它传给了 EventUtil.stopPropagation()。不过这个方法在浏览器上可能会停止事件冒泡也可能会既停止事件冒泡也停止事件捕获。 事件类型 Web 浏览器中可以发生很多种事件。如前所述所发生事件的类型决定了事件对象中会保存什么信息。DOM3 Events 定义了如下事件类型。 用户界面事件UIEvent涉及与 BOM 交互的通用浏览器事件。焦点事件FocusEvent在元素获得和失去焦点时触发。鼠标事件MouseEvent使用鼠标在页面上执行某些操作时触发。滚轮事件WheelEvent使用鼠标滚轮或类似设备时触发。输入事件InputEvent向文档中输入文本时触发。键盘事件KeyboardEvent使用键盘在页面上执行某些操作时触发。合成事件CompositionEvent在使用某种 IMEInput Method Editor输入法编辑器输入字符时触发。 除了这些事件类型之外HTML5 还定义了另一组事件而浏览器通常在 DOM 和 BOM 上实现专有事件。这些专有事件基本上都是根据开发者需求而不是按照规范增加的因此不同浏览器的实现可能不同。DOM3 Events 在 DOM2 Events 基础上重新定义了事件并增加了新的事件类型。所有主流浏览器都支持 DOM2 Events 和 DOM3 Events。 用户界面事件 用户界面事件或 UI 事件不一定跟用户操作有关。这类事件在 DOM 规范出现之前就已经以某种形式存在了保留它们是为了向后兼容。UI 事件主要有以下几种。 DOMActivate元素被用户通过鼠标或键盘操作激活时触发比 click 或 keydown 更通用。这个事件在 DOM3 Events 中已经废弃。因为浏览器实现之间存在差异所以不要使用它。load在 window 上当页面加载完成后触发在窗套frameset上当所有窗格frame都加载完成后触发在img元素上当图片加载完成后触发在object元素上当相应对象加载完成后触发。unload在 window 上当页面完全卸载后触发在窗套上当所有窗格都卸载完成后触发在object元素上当相应对象卸载完成后触发。abort在object元素上当相应对象加载完成前被用户提前终止下载时触发。error在 window 上当 JavaScript 报错时触发在img元素上当无法加载指定图片时触发在object元素上当无法加载相应对象时触发在窗套上当一个或多个窗格无法完成加载时触发。select在文本框input或 textarea上当用户选择了一个或多个字符时触发。resize在 window 或窗格上当窗口或窗格被缩放时触发。scroll当用户滚动包含滚动条的元素时在元素上触发。body元素包含已加载页面的滚动条。 大多数 HTML 事件与 window 对象和表单控件有关。除了 DOMActivate这些事件在 DOM2 Events 中都被归为 HTML EventsDOMActivate 在 DOM2中仍旧是 UI 事件。 load 事件 load 事件可能是 JavaScript 中最常用的事件。在 window 对象上load 事件会在整个页面包括所有外部资源如图片、JavaScript 文件和 CSS 文件加载完成后触发。可以通过两种方式指定 load 事件处理程序。第一种是 JavaScript 方式如下所示window.addEventListener(load, (event) { console.log(Loaded!); });这是使用 addEventListener()方法来指定事件处理程序。与其他事件一样事件处理程序会接收到一个 event 对象。这个 event 对象并没有提供关于这种类型事件的额外信息虽然在 DOM 合规的浏览器中event.target 会被设置为 document但在 IE8 之前的版本中不会设置这个对象的srcElement 属性。第二种指定 load 事件处理程序的方式是向body元素添加 onload 属性如下所示!DOCTYPE html html head titleLoad Event Example/title /headbody onloadconsole.log(Loaded!) /body /html一般来说任何在 window 上发生的事件都可以通过给body元素上对应的属性赋值来指定这是因为 HTML 中没有 window 元素。这实际上是为了保证向后兼容的一个策略但在所有浏览器中都能得到很好的支持。实际开发中要尽量使用 JavaScript 方式。注意根据 DOM2 Eventsload 事件应该在 document 而非 window 上触发。可是为了向后兼容所有浏览器都在 window 上实现了 load 事件。图片上也会触发load事件包括DOM中的图片和非DOM中的图片。可以在HTML中直接给img元素的 onload 属性指定事件处理程序比如img srcsmile.gif onloadconsole.log(Image loaded.)这个例子会在图片加载完成后输出一条消息。同样使用 JavaScript 也可以为图片指定事件处理程序let image document.getElementById(myImage); image.addEventListener(load, (event) { console.log(event.target.src); });这里使用 JavaScript 为图片指定了 load 事件处理程序。处理程序会接收到 event 对象虽然这个对象上没有多少有用的信息。这个事件的目标是img元素因此可以直接从 event.target.src 属性中取得图片地址并打印出来。 在通过 JavaScript 创建新img元素时也可以给这个元素指定一个在加载完成后执行的事件处理程序。在这里关键是要在赋值 src 属性前指定事件处理程序如下所示window.addEventListener(load, () { let image document.createElement(img); image.addEventListener(load, (event) { console.log(event.target.src); }); document.body.appendChild(image); image.src smile.gif; });这个例子首先为 window 指定了一个 load 事件处理程序。因为示例涉及向 DOM 中添加新元素所以必须确保页面已经加载完成。如果在页面加载完成之前操作 document.body则会导致错误。然后代码创建了一个新的img元素并为这个元素设置了 load 事件处理程序。最后才把这个元素添加到文档中并指定了其 src 属性。注意下载图片并不一定要把img元素添加到文档只要给它设置了 src 属性就会立即开始下载。同样的技术也适用于 DOM0 的 Image 对象。在 DOM 出现之前客户端都使用 Image 对象预先加载图片。可以像使用前面通过 createElement()方法创建的img元素一样使用 Image 对象只是不能把后者添加到 DOM 树。下面的例子使用新 Image 对象实现了图片预加载window.addEventListener(load, () { let image new Image(); image.addEventListener(load, (event) { console.log(Image loaded!);}); image.src smile.gif; });这里调用 Image 构造函数创建了一个新图片并给它设置了事件处理程序。有些浏览器会把 Image对象实现为img元素但并非所有浏览器都如此。所以最好把它们看成是两个东西。注意在 IE8 及早期版本中如果图片没有添加到 DOM 文档中则 load 事件发生时不会生成 event 对象。对未被添加到文档中的img元素以及 Image 对象来说都是这样。IE9 修复了这个问题。还有一些元素也以非标准的方式支持 load 事件。script元素会在 JavaScript 文件加载完成后触发 load 事件从而可以动态检测。与图片不同要下载 JavaScript 文件必须同时指定 src 属性并把script元素添加到文档中。因此指定事件处理程序和指定 src 属性的顺序在这里并不重要。下面的代码展示了如何给动态创建的script元素指定事件处理程序window.addEventListener(load, () { let script document.createElement(script); script.addEventListener(load, (event) { console.log(Loaded); }); script.src example.js; document.body.appendChild(script); });这里 event 对象的 target 属性在大多数浏览器中是script节点。IE8及更早版本不支持script元素触发 load 事件。 IE 和 Opera 支持link元素触发 load 事件因而支持动态检测样式表是否加载完成。下面的代码展示了如何设置这样的事件处理程序window.addEventListener(load, () { let link document.createElement(link); link.type text/css; link.rel stylesheet; link.addEventListener(load, (event) { console.log(css loaded); }); link.href example.css; document.getElementsByTagName(head)[0].appendChild(link); });与script节点一样在指定 href 属性并把link节点添加到文档之前不会下载样式表。 unload 事件 与 load 事件相对的是 unload 事件unload 事件会在文档卸载完成后触发。unload 事件一般是在从一个页面导航到另一个页面时触发最常用于清理引用以避免内存泄漏。与 load 事件类似unload 事件处理程序也有两种指定方式。第一种是 JavaScript 方式如下所示window.addEventListener(unload, (event) { console.log(Unloaded!); });这个事件生成的 event 对象在 DOM 合规的浏览器中只有 target 属性值为 document。IE8 及更早版本在这个事件上不提供 srcElement 属性。 第二种方式与 load 事件类似就是给body元素添加 onunload 属性!DOCTYPE html html head titleUnload Event Example/title /head body onunloadconsole.log(Unloaded!) /body /html无论使用何种方式都要注意事件处理程序中的代码。因为 unload 事件是在页面卸载完成后触发的所以不能使用页面加载后才有的对象。此时要访问 DOM 或修改页面外观都会导致错误。注意根据 DOM2 Eventsunload 事件应该在body而非 window 上触发。可是为了向后兼容所有浏览器都在 window 上实现了 unload 事件。 resize 事件 当浏览器窗口被缩放到新高度或宽度时会触发 resize 事件。这个事件在 window 上触发因此可以通过 JavaScript 在 window 上或者为body元素添加 onresize 属性来指定事件处理程序。优先使用 JavaScript 方式window.addEventListener(resize, (event) { console.log(Resized); });类似于其他在 window 上发生的事件此时会生成 event 对象且这个对象的 target 属性在 DOM合规的浏览器中是 document。而 IE8 及更早版本中并没有提供可用的属性。不同浏览器在决定何时触发 resize 事件上存在重要差异。IE、Safari、Chrome 和 Opera 会在窗口缩放超过 1 像素时触发 resize 事件然后随着用户缩放浏览器窗口不断触发。Firefox 早期版本则只在用户停止缩放浏览器窗口时触发 resize 事件。无论如何都应该避免在这个事件处理程序中执行过多计算。否则可能由于执行过于频繁而导致浏览器响应明确变慢。注意浏览器窗口在最大化和最小化时也会触发 resize 事件。 scroll 事件 虽然 scroll 事件发生在 window 上但实际上反映的是页面中相应元素的变化。在混杂模式下可以通过body元素检测 scrollLeft 和 scrollTop 属性的变化。而在标准模式下这些变化在除早期版的 Safari 之外的所有浏览器中都发生在html元素上早期版的 Safari 在body上跟踪滚动位置。下面的代码演示了如何处理这些差异window.addEventListener(scroll, (event) { if (document.compatMode CSS1Compat) { console.log(document.documentElement.scrollTop); } else { console.log(document.body.scrollTop); } });以上事件处理程序会在页面滚动时输出垂直方向上滚动的距离而且适用于不同渲染模式。因为Safari 3.1 之前不支持 document.compatMode所以早期版本会走第二个分支。类似于 resizescroll 事件也会随着文档滚动而重复触发因此最好保持事件处理程序的代码尽可能简单。 焦点事件 焦点事件在页面元素获得或失去焦点时触发。这些事件可以与 document.hasFocus()和document.activeElement 一起为开发者提供用户在页面中导航的信息。焦点事件有以下 6 种。 blur当元素失去焦点时触发。这个事件不冒泡所有浏览器都支持。DOMFocusIn当元素获得焦点时触发。这个事件是 focus 的冒泡版。Opera 是唯一支持这个事件的主流浏览器。DOM3 Events 废弃了 DOMFocusIn推荐 focusin。DOMFocusOut当元素失去焦点时触发。这个事件是 blur 的通用版。Opera 是唯一支持这个事件的主流浏览器。DOM3 Events 废弃了 DOMFocusOut推荐 focusout。focus当元素获得焦点时触发。这个事件不冒泡所有浏览器都支持。focusin当元素获得焦点时触发。这个事件是 focus 的冒泡版。focusout当元素失去焦点时触发。这个事件是 blur 的通用版。 焦点事件中的两个主要事件是 focus 和 blur这两个事件在 JavaScript 早期就得到了浏览器支持。它们最大的问题是不冒泡。这导致 IE后来又增加了 focusin 和 focusoutOpera又增加了 DOMFocusIn和 DOMFocusOut。IE 新增的这两个事件已经被 DOM3 Events 标准化。当焦点从页面中的一个元素移到另一个元素上时会依次发生如下事件。 (1) focuscout 在失去焦点的元素上触发。(2) focusin 在获得焦点的元素上触发。(3) blur 在失去焦点的元素上触发。(4) DOMFocusOut 在失去焦点的元素上触发。(5) focus 在获得焦点的元素上触发。(6) DOMFocusIn 在获得焦点的元素上触发。 其中blur、DOMFocusOut 和 focusout 的事件目标是失去焦点的元素而 focus、DOMFocusIn和 focusin 的事件目标是获得焦点的元素。 鼠标和滚轮事件 鼠标事件是 Web 开发中最常用的一组事件这是因为鼠标是用户的主要定位设备。DOM3 Events定义了 9 种鼠标事件。 click在用户单击鼠标主键通常是左键或按键盘回车键时触发。这主要是基于无障碍的考虑让键盘和鼠标都可以触发 onclick 事件处理程序。dblclick在用户双击鼠标主键通常是左键时触发。这个事件不是在 DOM2 Events 中定义的但得到了很好的支持DOM3 Events 将其进行了标准化。mousedown在用户按下任意鼠标键时触发。这个事件不能通过键盘触发。mouseenter在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不冒泡也不会在光标经过后代元素时触发。mouseenter 事件不是在 DOM2 Events 中定义的而是 DOM3 Events中新增的事件。mouseleave在用户把鼠标光标从元素内部移到元素外部时触发。这个事件不冒泡也不会在光标经过后代元素时触发。mouseleave 事件不是在 DOM2 Events 中定义的而是 DOM3 Events中新增的事件。mousemove在鼠标光标在元素上移动时反复触发。这个事件不能通过键盘触发。mouseout在用户把鼠标光标从一个元素移到另一个元素上时触发。移到的元素可以是原始元素的外部元素也可以是原始元素的子元素。这个事件不能通过键盘触发。mouseover在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不能通过键盘触发。mouseup在用户释放鼠标键时触发。这个事件不能通过键盘触发。 页面中的所有元素都支持鼠标事件。除了 mouseenter 和 mouseleave所有鼠标事件都会冒泡都可以被取消而这会影响浏览器的默认行为。由于事件之间存在关系因此取消鼠标事件的默认行为也会影响其他事件。比如click 事件触发的前提是 mousedown 事件触发后紧接着又在同一个元素上触发了 mouseup事件。如果 mousedown 和 mouseup 中的任意一个事件被取消那么 click 事件就不会触发。类似地两次连续的 click 事件会导致 dblclick 事件触发。只要有任何逻辑阻止了这两个 click 事件发生比如取消其中一个 click 事件或者取消 mousedown 或 mouseup 事件中的任一个dblclick 事件就不会发生。这 4 个事件永远会按照如下顺序触发 (1) mousedown(2) mouseup(3) click(4) mousedown(5) mouseup(6) click(7) dblclick click 和 dblclick 在触发前都依赖其他事件触发mousedown 和 mouseup 则不会受其他事件影响。IE8 及更早版本的实现中有个问题这会导致双击事件跳过第二次 mousedown 和 click 事件。相应的顺序变成了 (1) mousedown(2) mouseup(3) click(4) mouseup(5) dblclick 鼠标事件在 DOM3 Events 中对应的类型是MouseEvent而不是MouseEvents。鼠标事件还有一个名为滚轮事件的子类别。滚轮事件只有一个事件 mousewheel反映的是鼠标滚轮或带滚轮的类似设备上滚轮的交互。 客户端坐标 鼠标事件都是在浏览器视口中的某个位置上发生的。这些信息被保存在 event 对象的 clientX 和clientY 属性中。这两个属性表示事件发生时鼠标光标在视口中的坐标所有浏览器都支持。可以通过下面的方式获取鼠标事件的客户端坐标let div document.getElementById(myDiv); div.addEventListener(click, (event) { console.log(Client coordinates: ${event.clientX}, ${event.clientY}); });这个例子为div元素指定了一个 onclick 事件处理程序。当元素被点击时会显示事件发生时鼠标光标在客户端视口中的坐标。注意客户端坐标不考虑页面滚动因此这两个值并不代表鼠标在页面上的位置。 页面坐标 客户端坐标是事件发生时鼠标光标在客户端视口中的坐标而页面坐标是事件发生时鼠标光标在页面上的坐标通过 event 对象的 pageX 和 pageY 可以获取。这两个属性表示鼠标光标在页面上的位置因此反映的是光标到页面而非视口左边与上边的距离。可以像下面这样取得鼠标事件的页面坐标let div document.getElementById(myDiv); div.addEventListener(click, (event) { console.log(Page coordinates: ${event.pageX}, ${event.pageY}); });在页面没有滚动时pageX 和 pageY 与 clientX 和 clientY 的值相同。IE8 及更早版本没有在 event 对象上暴露页面坐标。不过可以通过客户端坐标和滚动信息计算出来。滚动信息可以从 document.body混杂模式或 document.documentElement标准模式的scrollLeft 和 scrollTop 属性获取。计算过程如下所示let div document.getElementById(myDiv); div.addEventListener(click, (event) { let pageX event.pageX, pageY event.pageY; if (pageX undefined) { pageX event.clientX (document.body.scrollLeft || document.documentElement.scrollLeft); } if (pageY undefined) { pageY event.clientY (document.body.scrollTop || document.documentElement.scrollTop); } console.log(Page coordinates: ${pageX}, ${pageY}); });屏幕坐标 鼠标事件不仅是在浏览器窗口中发生的也是在整个屏幕上发生的。可以通过 event 对象的screenX 和 screenY 属性获取鼠标光标在屏幕上的坐标。可以像下面这样获取鼠标事件的屏幕坐标let div document.getElementById(myDiv); div.addEventListener(click, (event) { console.log(Screen coordinates: ${event.screenX}, ${event.screenY}); });与前面的例子类似这段代码也为div元素指定了 onclick 事件处理程序。当元素被点击时会通过控制台打印出事件的屏幕坐标。 修饰键 虽然鼠标事件主要是通过鼠标触发的但有时候要确定用户想实现的操作还要考虑键盘按键的状态。键盘上的修饰键 Shift、Ctrl、Alt 和 Meta 经常用于修改鼠标事件的行为。DOM 规定了 4 个属性来表示这几个修饰键的状态shiftKey、ctrlKey、altKey 和 metaKey。这几属性会在各自对应的修饰键被按下时包含布尔值 true没有被按下时包含 false。在鼠标事件发生的可以通过这几个属性来检测修饰键是否被按下。来看下面的例子其中在 click 事件发生时检测了每个修饰键的状态let div document.getElementById(myDiv); div.addEventListener(click, (event) { let keys new Array(); if (event.shiftKey) { keys.push(shift); } if (event.ctrlKey) { keys.push(ctrl); } if (event.altKey) { keys.push(alt); } if (event.metaKey) { keys.push(meta); } console.log(Keys: keys.join(,)); });在这个例子中onclick 事件处理程序检查了不同修饰键的状态。keys 数组中包含了在事件发生时被按下的修饰键的名称。每个对应属性为 true 的修饰键的名称都会添加到 keys 中。最后事件处理程序会输出所有键的名称。 注意现代浏览器支持所有这 4 个修饰键。IE8 及更早版本不支持 metaKey 属性。 相关元素 对 mouseover 和 mouseout 事件而言还存在与事件相关的其他元素。这两个事件都涉及从一个元素的边界之内把光标移到另一个元素的边界之内。对 mouseover 事件来说事件的主要目标是获得光标的元素相关元素是失去光标的元素。类似地对 mouseout 事件来说事件的主要目标是失去光标的元素而相关元素是获得光标的元素。来看下面的例子!DOCTYPE html html head titleRelated Elements Example/title /head body div idmyDiv stylebackground-color:red;height:100px;width:100px;/div /body /html这个页面中只包含一个div元素。如果光标开始在div元素上然后从它上面移出则div元素上会触发 mouseout 事件相关元素为body元素。与此同时body元素上会触发 mouseover事件相关元素是div元素。 DOM通过 event 对象的 relatedTarget 属性提供了相关元素的信息。这个属性只有在 mouseover和 mouseout 事件发生时才包含值其他所有事件的这个属性的值都是 null。IE8 及更早版本不支持relatedTarget 属性但提供了其他的可以访问到相关元素的属性。在 mouseover 事件触发时IE会提供 fromElement 属性其中包含相关元素。而在 mouseout 事件触发时IE 会提供 toElement属性其中包含相关元素。IE9 支持所有这些属性。因此可以在 EventUtil 中增加一个通用的获取相关属性的方法var EventUtil { // 其他代码getRelatedTarget: function(event) { if (event.relatedTarget) { return event.relatedTarget; } else if (event.toElement) { return event.toElement; } else if (event.fromElement) { return event.fromElement; } else { return null; } },// 其他代码 };与前面介绍的其他跨浏览器方法一样这个方法同样使用特性检测来确定要返回哪个值。可以像下面这样使用 EventUtil.getRelatedTarget()方法let div document.getElementById(myDiv); div.addEventListener(mouseout, (event) { let target event.target; let relatedTarget EventUtil.getRelatedTarget(event); console.log( Moused out of ${target.tagName} to ${relatedTarget.tagName}); });这个例子在div元素上注册了 mouseout 事件处理程序。当事件触发时就会打印出一条消息说明鼠标从哪个元素移出移到了哪个元素上。 鼠标按键 只有在元素上单击鼠标主键或按下键盘上的回车键时 click 事件才会触发因此按键信息并不是必需的。对 mousedown 和 mouseup 事件来说event 对象上会有一个 button 属性表示按下或释放的是哪个按键。DOM 为这个 button 属性定义了 3 个值0 表示鼠标主键、1 表示鼠标中键通常也是滚轮键、2 表示鼠标副键。按照惯例鼠标主键通常是左边的按键副键通常是右边的按键。IE8 及更早版本也提供了 button 属性但这个属性的值与前面说的完全不同 0表示没有按下任何键1表示按下鼠标主键2表示按下鼠标副键3表示同时按下鼠标主键、副键4表示按下鼠标中键5表示同时按下鼠标主键和中键6表示同时按下鼠标副键和中键7表示同时按下 3 个键。 很显然DOM 定义的 button 属性比 IE 这一套更简单也更有用毕竟同时按多个鼠标键的情况很少见。为此实践中基本上都以 DOM 的 button 属性为准这是因为除 IE8 及更早版本外的所有主流浏览器都原生支持。主、中、副键的定义非常明确而 IE 定义的其他情形都可以翻译为按下其中某个键而且优先翻译为主键。比如IE 返回 5 或 7 时就会对应到 DOM 的 0。 额外事件信息 DOM2 Events 规范在 event 对象上提供了 detail 属性以给出关于事件的更多信息。对鼠标事件来说detail 包含一个数值表示在给定位置上发生了多少次单击。单击相当于在同一个像素上发生一次 mousedown 紧跟一次 mouseup。detail 的值从 1 开始每次单击会加 1。如果鼠标在 mousedown和 mouseup 之间移动了则 detail 会重置为 0。IE 还为每个鼠标事件提供了以下额外信息 altLeft布尔值表示是否按下了左 Alt 键如果 altLeft 是 true那么 altKey 也是 truectrlLeft布尔值表示是否按下了左 Ctrl 键如果 ctrlLeft 是 true那么 ctrlKey 也是trueoffsetX光标相对于目标元素边界的 x 坐标offsetY光标相对于目标元素边界的 y 坐标shiftLeft布尔值表示是否按下了左 Shift 键如果 shiftLeft 是 true那么 shiftKey也是 true。 这些属性的作用有限这是因为只有 IE 支持。而且它们提供的信息要么没必要要么可以通过其他方式计算。 mousewheel 事件 IE6 首先实现了 mousewheel 事件。之后Opera、Chrome 和 Safari 也跟着实现了。mousewheel事件会在用户使用鼠标滚轮时触发包括在垂直方向上任意滚动。这个事件会在任何元素上触发并在IE8 中冒泡到 document 和在所有现代浏览器中window。mousewheel 事件的 event 对象包含鼠标事件的所有标准信息此外还有一个名为 wheelDelta 的新属性。当鼠标滚轮向前滚动时wheelDelta 每次都是120而当鼠标滚轮向后滚动时wheelDelta 每次都是–120。可以为页面上的任何元素或文档添加 onmousewheel 事件处理程序以处理所有鼠标滚轮交互比如document.addEventListener(mousewheel, (event) { console.log(event.wheelDelta); });这个例子简单地显示了鼠标滚轮事件触发时 wheelDelta 的值。多数情况下只需知道滚轮滚动的方向而这通过 wheelDelta 值的符号就可以知道。 注意HTML5 也增加了 mousewheel 事件以反映大多数浏览器对它的支持。 触摸屏设备 iOS 和 Android 等触摸屏设备的实现大相径庭因为触摸屏通常不支持鼠标操作。在为触摸屏设备开发时要记住以下事项。 不支持 dblclick 事件。双击浏览器窗口可以放大但没有办法覆盖这个行为。单指点触屏幕上的可点击元素会触发 mousemove 事件。如果操作会导致内容变化则不会再触发其他事件。如果屏幕上没有变化则会相继触发 mousedown、mouseup 和 click 事件。点触不可点击的元素不会触发事件。可点击元素是指点击时有默认动作的元素如链接或指定了 onclick 事件处理程序的元素。mousemove 事件也会触发 mouseover 和 mouseout 事件。双指点触屏幕并滑动导致页面滚动时会触发 mousewheel 和 scroll 事件。 无障碍问题 如果 Web 应用或网站必须考虑残障人士特别是使用屏幕阅读器的用户那么必须小心使用鼠标事件。如前所述按回车键可以触发 click 事件但其他鼠标事件不能通过键盘触发。因此建议不要使用 click 事件之外的其他鼠标事件向用户提示功能或触发代码执行这是因为其他鼠标事件会严格妨碍盲人或视障用户使用。以下是几条使用鼠标事件时应该遵循的无障碍建议。 使用 click 事件执行代码。有人认为当使用 onmousedown 执行代码时应用程序会运行得更快。对视力正常用户来说确实如此。但在屏幕阅读器上这样会导致代码无法执行这是因为屏幕阅读器无法触发 mousedown 事件。不要使用 mouseover 向用户显示新选项。同样原因是屏幕阅读器无法触发 mousedown 事件。如果必须要通过这种方式显示新选项那么可以考虑显示相同信息的键盘快捷键。不要使用 dblclick 执行重要的操作这是因为键盘不能触发这个事件。 遵循这些简单的建议可以极大提升 Web 应用或网站对残障人士的无障碍性。注意要了解更多关于网站无障碍的信息可以参考 WebAIM 网站。 键盘与输入事件 键盘事件是用户操作键盘时触发的。DOM2 Events 最初定义了键盘事件但该规范在最终发布前删除了相应内容。因此键盘事件很大程度上是基于原始的 DOM0 实现的。DOM3 Events 为键盘事件提供了一个首先在 IE9 中完全实现的规范。其他浏览器也开始实现该规范但仍然存在很多遗留的实现。键盘事件包含 3 个事件 keydown用户按下键盘上某个键时触发而且持续按住会重复触发。keypress用户按下键盘上某个键并产生字符时触发而且持续按住会重复触发。Esc 键也会触发这个事件。DOM3 Events 废弃了 keypress 事件而推荐 textInput 事件。keyup用户释放键盘上某个键时触发。 虽然所有元素都支持这些事件但当用户在文本框中输入内容时最容易看到。输入事件只有一个即 textInput。这个事件是对 keypress 事件的扩展用于在文本显示给用户之前更方便地截获文本输入。textInput 会在文本被插入到文本框之前触发。当用户按下键盘上的某个字符键时首先会触发 keydown 事件然后触发 keypress 事件最后触发 keyup 事件。注意这里 keydown 和 keypress 事件会在文本框出现变化之前触发而 keyup事件会在文本框出现变化之后触发。如果一个字符键被按住不放keydown 和 keypress 就会重复触发直到这个键被释放。对于非字符键在键盘上按一下这个键会先触发 keydown 事件然后触发 keyup 事件。如果按住某个非字符键不放则会重复触发 keydown 事件直到这个键被释放此时会触发 keyup 事件。注意键盘事件支持与鼠标事件相同的修饰键。shiftKey、ctrlKey、altKey 和metaKey属性在键盘事件中都是可用的。IE8 及更早版本不支持 metaKey 属性。 键码 对于 keydown 和 keyup 事件event 对象的 keyCode 属性中会保存一个键码对应键盘上特定的一个键。对于字母和数字键keyCode 的值与小写字母和数字的 ASCII 编码一致。比如数字 7 键的keyCode 为 55而字母 A 键的 keyCode 为 65而且跟是否按了 Shift 键无关。DOM 和 IE 的 event 对象都支持 keyCode 属性。下面这个例子展示了如何使用 keyCode 属性 let textbox document.getElementById(myText); textbox.addEventListener(keyup, (event) { console.log(event.keyCode); });这个例子在 keyup 事件触发时直接显示出 event 对象的 keyCode 属性值。 下表给出了键盘上所有非字符键的键码。 键键码Backspace8Tab9Enter13Shift16Ctrl17Alt18Pause/Break19Caps Lock20Esc27Page Up33Page Down34End35Home36左箭头37上箭头38右箭头39下箭头40Ins45Del46左 Windows91右 Windows92Context Menu93数字键盘 096数字键盘 197数字键盘 298数字键盘 399数字键盘 4100数字键盘 5101数字键盘 6102数字键盘 7103数字键盘 8104数字键盘 9105数字键盘107减号包含数字和非数字键盘109数字键盘.110数字键盘/111F1112F2113F3114F4115F5116F6117F7118F8119F9120F10121F11122F12123Num Lock144Scroll Lock145分号IE/Safari/Chrome186分号Opera/FF59小于号188大于号190反斜杠191重音符192等于号61左中括号219反斜杠\220右中括号221单引号222 字符编码 在 keypress 事件发生时意味着按键会影响屏幕上显示的文本。对插入或移除字符的键所有浏览器都会触发 keypress 事件其他键则取决于浏览器。因为 DOM3 Events 规范才刚刚开始实现所以不同浏览器之间的实现存在显著差异。浏览器在 event 对象上支持 charCode 属性只有发生 keypress 事件时这个属性才会被设置值包含的是按键字符对应的 ASCII 编码。通常charCode 属性的值是 0在 keypress 事件发生时则是对应按键的键码。IE8 及更早版本和 Opera 使用 keyCode 传达字符的 ASCII 编码。要以跨浏览器方式获取字符编码首先要检查 charCode 属性是否有值如果没有再使用 keyCode如下所示var EventUtil { // 其他代码getCharCode: function(event) { if (typeof event.charCode number) { return event.charCode; } else { return event.keyCode; } }, // 其他代码 };这个方法检测 charCode 属性是否为数值在不支持的浏览器中是 undefined。如果是数值则返回。否则返回 keyCode 值。可以像下面这样使用let textbox document.getElementById(myText); textbox.addEventListener(keypress, (event) { console.log(EventUtil.getCharCode(event)); });一旦有了字母编码就可以使用 String.fromCharCode()方法将其转换为实际的字符了。 DOM3 的变化 尽管所有浏览器都实现了某种形式的键盘事件DOM3 Events 还是做了一些修改。比如DOM3 Events 规范并未规定 charCode 属性而是定义了 key 和 char 两个新属性。其中key 属性用于替代 keyCode且包含字符串。在按下字符键时key 的值等于文本字符如“k”或“M”在按下非字符键时key 的值是键名如“Shift”或“ArrowDown”。char 属性在按下字符键时与 key 类似在按下非字符键时为 null。IE 支持 key 属性但不支持 char 属性。Safari 和 Chrome 支持 keyIdentifier 属性在按下非字符键时返回与 key 一样的值如“Shift”。对于字符键keyIdentifier 返回以“U0000”形式表示Unicode 值的字符串形式的字符编码。let textbox document.getElementById(myText); textbox.addEventListener(keypress, (event) { let identifier event.key || event.keyIdentifier; if (identifier) { console.log(identifier); } });由于缺乏跨浏览器支持因此不建议使用 key、keyIdentifier、和 char。DOM3 Events 也支持一个名为 location 的属性该属性是一个数值表示是在哪里按的键。可能的值为0 是默认键1 是左边如左边的 Alt 键2 是右边如右边的 Shift 键3 是数字键盘4 是移动设备即虚拟键盘5 是游戏手柄如任天堂 Wii 控制器。IE9 支持这些属性。Safari 和 Chrome支持一个等价的 keyLocation 属性但由于实现有问题这个属性值始终为 0除非是数字键盘此时值为 3值永远不会是 1、2、4、5。let textbox document.getElementById(myText); textbox.addEventListener(keypress, (event) { let loc event.location || event.keyLocation; if (loc) { console.log(loc); } });与 key 属性类似location 属性也没有得到广泛支持因此不建议在跨浏览器开发时使用。最后一个变化是给 event 对象增加了 getModifierState()方法。这个方法接收一个参数一个等于 Shift、Control、Alt、AltGraph 或 Meta 的字符串表示要检测的修饰键。如果给定的修饰键处于激活状态键被按住则方法返回 true否则返回 falselet textbox document.getElementById(myText); textbox.addEventListener(keypress, (event) { if (event.getModifierState) { console.log(event.getModifierState(Shift)); } });当然event 对象已经通过 shiftKey、altKey、ctrlKey 和 metaKey 属性暴露了这些信息。 textInput 事件 DOM3 Events 规范增加了一个名为 textInput 的事件其在字符被输入到可编辑区域时触发。作为对 keypress 的替代textInput 事件的行为有些不一样。一个区别是 keypress 会在任何可以获得焦点的元素上触发而 textInput 只在可编辑区域上触发。另一个区别是 textInput 只在有新字符被插入时才会触发而 keypress 对任何可能影响文本的键都会触发包括退格键。因为 textInput 事件主要关注字符所以在 event 对象上提供了一个 data 属性包含要插入的字符不是字符编码。data 的值始终是要被插入的字符因此如果在按 S 键时没有按 Shift 键data的值就是s但在按 S 键时同时按 Shift 键data 的值则是S。textInput 事件可以这样来用let textbox document.getElementById(myText); textbox.addEventListener(textInput, (event) { console.log(event.data); });这个例子会实时把输入文本框的文本通过日志打印出来。 event 对象上还有一个名为 inputMethod 的属性该属性表示向控件中输入文本的手段。可能的值如下 0表示浏览器不能确定是什么输入手段1表示键盘2表示粘贴3表示拖放操作4表示 IME5表示表单选项6表示手写如使用手写笔7表示语音8表示组合方式9表示脚本。 使用这些属性可以确定用户是如何将文本输入到控件中的从而可以辅助验证。 设备上的键盘事件 任天堂 Wii 会在用户按下 Wii 遥控器上的键时触发键盘事件。虽然不能访问 Wii 遥控器上所有的键但其中一些键可以触发键盘事件。 合成事件 合成事件是 DOM3 Events 中新增的用于处理通常使用 IME 输入时的复杂输入序列。IME 可以让用户输入物理键盘上没有的字符。例如使用拉丁字母键盘的用户还可以使用 IME 输入日文。IME 通常需要同时按下多个键才能输入一个字符。合成事件用于检测和控制这种输入。合成事件有以下 3 种 compositionstart在 IME 的文本合成系统打开时触发表示输入即将开始compositionupdate在新字符插入输入字段时触发compositionend在 IME 的文本合成系统关闭时触发表示恢复正常键盘输入。 合成事件在很多方面与输入事件很类似。在合成事件触发时事件目标是接收文本的输入字段。唯一增加的事件属性是 data其中包含的值视情况而异 在 compositionstart 事件中包含正在编辑的文本例如已经选择了文本但还没替换在 compositionupdate 事件中包含要插入的新字符在 compositionend 事件中包含本次合成过程中输入的全部内容。 与文本事件类似合成事件可以用来在必要时过滤输入内容。可以像下面这样使用合成事件let textbox document.getElementById(myText); textbox.addEventListener(compositionstart, (event) { console.log(event.data); }); textbox.addEventListener(compositionupdate, (event) { console.log(event.data); }); textbox.addEventListener(compositionend, (event) { console.log(event.data); });变化事件 DOM2 的变化事件Mutation Events是为了在 DOM 发生变化时提供通知。注意这些事件已经被废弃浏览器已经在有计划地停止对它们的支持。变化事件已经被Mutation Observers 所取代可以参考第 14 章中的介绍。 HTML5 事件 DOM 规范并未涵盖浏览器都支持的所有事件。很多浏览器根据特定的用户需求或使用场景实现了自定义事件。HTML5 详尽地列出了浏览器支持的所有事件。本节讨论 HTML5 中得到浏览器较好支持的一些事件。注意这些并不是浏览器支持的所有事件。本书后面也会涉及一些其他事件。 contextmenu 事件 Windows 95 通过单击鼠标右键为 PC 用户增加了上下文菜单的概念。不久这个概念也在 Web 上得以实现。开发者面临的问题是如何确定何时该显示上下文菜单在 Windows 上是右击鼠标在 Mac 上是 Ctrl单击以及如何避免默认的上下文菜单起作用。结果就出现了 contextmenu 事件以专门用于表示何时该显示上下文菜单从而允许开发者取消默认的上下文菜单并提供自定义菜单。contextmenu 事件冒泡因此只要给 document 指定一个事件处理程序就可以处理页面上的所有同类事件。事件目标是触发操作的元素。这个事件在所有浏览器中都可以取消在 DOM 合规的浏览器中使用 event.preventDefault()在 IE8 及更早版本中将 event.returnValue 设置为 false。contextmenu 事件应该算一种鼠标事件因此 event 对象上的很多属性都与光标位置有关。通常自定义的上下文菜单都是通过 oncontextmenu 事件处理程序触发显示并通过 onclick 事件处理程序触发隐藏的。来看下面的例子!DOCTYPE html html head titleContextMenu Event Example/title /head bodydiv idmyDivRight click or Ctrlclick me to get a custom context menu. Click anywhere else to get the default context menu./div ul idmyMenu styleposition:absolute;visibility:hidden;background-color: silver lia hrefhttp://www.somewhere.com somewhere/a/li lia hrefhttp://www.wrox.comWrox site/a/li lia hrefhttp://www.somewhere-else.comsomewhere-else/a/li /ul /body /html这个例子中的div元素有一个上下文菜单ul。作为上下文菜单ul元素初始时是隐藏的。以下是实现上下文菜单功能的 JavaScript 代码window.addEventListener(load, (event) { let div document.getElementById(myDiv); div.addEventListener(contextmenu, (event) { event.preventDefault(); let menu document.getElementById(myMenu); menu.style.left event.clientX px; menu.style.top event.clientY px; menu.style.visibility visible; }); document.addEventListener(click, (event) { document.getElementById(myMenu).style.visibility hidden; }); });这里在div元素上指定了一个 oncontextmenu 事件处理程序。这个事件处理程序首先取消默认行确保不会显示浏览器默认的上下文菜单。接着基于 event 对象的 clientX 和 clientY 属性把ul元素放到适当位置。最后一步通过将 visibility 属性设置为visible让自定义上下文菜单显示出来。另外又给 document 添加了一个 onclick 事件处理程序以便在单击事件发生时隐藏上下文菜单系统上下文菜单就是这样隐藏的。虽然这个例子很简单但它是网页中所有自定义上下文菜单的基础。在这个简单例子的基础上再添加一些 CSS上下文菜单就会更漂亮。 beforeunload 事件 beforeunload 事件会在 window 上触发用意是给开发者提供阻止页面被卸载的机会。这个事件会在页面即将从浏览器中卸载时触发如果页面需要继续使用则可以不被卸载。这个事件不能取消否则就意味着可以把用户永久阻拦在一个页面上。相反这个事件会向用户显示一个确认框其中的消息表明浏览器即将卸载页面并请用户确认是希望关闭页面还是继续留在页面上。为了显示类似的确认框需要将 event.returnValue 设置为要在确认框中显示的字符串对于 IE 和 Firefox 来说并将其作为函数值返回对于 Safari 和 Chrome 来说如下所示window.addEventListener(beforeunload, (event) { let message Im really going to miss you if you go.; event.returnValue message; return message; });DOMContentLoaded 事件 window 的 load 事件会在页面完全加载后触发因为要等待很多外部资源加载完成所以会花费较长时间。而 DOMContentLoaded 事件会在 DOM 树构建完成后立即触发而不用等待图片、JavaScript文件、CSS 文件或其他资源加载完成。相对于 load 事件DOMContentLoaded 可以让开发者在外部资源下载的同时就能指定事件处理程序从而让用户能够更快地与页面交互。要处理 DOMContentLoaded 事件需要给 document 或 window 添加事件处理程序实际的事件目标是 document但会冒泡到 window。下面是一个在 document 上监听 DOMContentLoaded 事件的例子document.addEventListener(DOMContentLoaded, (event) { console.log(Content loaded); });DOMContentLoaded 事件的 event 对象中不包含任何额外信息除了 target 等于 document。DOMContentLoaded 事件通常用于添加事件处理程序或执行其他 DOM操作。这个事件始终在 load事件之前触发。对于不支持 DOMContentLoaded 事件的浏览器可以使用超时为 0 的 setTimeout()函数通过其回调来设置事件处理程序比如setTimeout(() { // 在这里添加事件处理程序 }, 0);以上代码本质上意味着在当前 JavaScript 进程执行完毕后立即执行这个回调。页面加载和构建期间只有一个 JavaScript 进程运行。所以可以在这个进程空闲后立即执行回调至于是否与同一个浏览器或同一页面上不同脚本的 DOMContentLoaded 触发时机一致并无绝对把握。为了尽可能早一些执行以上代码最好是页面上的第一个超时代码。即使如此考虑到各种影响因素也不一定保证能在 load 事件之前执行超时回调。 readystatechange 事件 IE 首先在 DOM 文档的一些地方定义了一个名为 readystatechange 事件。这个有点神秘的事件旨在提供文档或元素加载状态的信息但行为有时候并不稳定。支持 readystatechange 事件的每个对象都有一个 readyState 属性该属性具有一个以下列出的可能的字符串值。 uninitialized对象存在并尚未初始化。loading对象正在加载数据。loaded对象已经加载完数据。interactive对象可以交互但尚未加载完成。complete对象加载完成。 看起来很简单其实并非所有对象都会经历所有 readystate 阶段。文档中说有些对象会完全跳过某个阶段但并未说明哪些阶段适用于哪些对象。这意味着 readystatechange 事件经常会触发不到4 次而 readyState 未必会依次呈现上述值。在 document 上使用时值为interactive的 readyState 首先会触发 readystatechange事件时机类似于 DOMContentLoaded。进入交互阶段意味着 DOM 树已加载完成因而可以安全地交互了。此时图片和其他外部资源不一定都加载完了。可以像下面这样使用 readystatechange 事件document.addEventListener(readystatechange, (event) { if (document.readyState interactive) { console.log(Content loaded); } });这个事件的 event 对象中没有任何额外的信息连事件目标都不会设置。在与 load 事件共同使用时这个事件的触发顺序不能保证。在包含特别多或较大外部资源的页面中交互阶段会在 load 事件触发前先触发。而在包含较少且较小外部资源的页面中这个readystatechange 事件有可能在 load 事件触发后才触发。让问题变得更加复杂的是交互阶段与完成阶段的顺序也不是固定的。在外部资源较多的页面中很可能交互阶段会早于完成阶段而在外部资源较少的页面中很可能完成阶段会早于交互阶段。因此实践中为了抢到较早的时机需要同时检测交互阶段和完成阶段。比如document.addEventListener(readystatechange, (event) { if (document.readyState interactive || document.readyState complete) { document.removeEventListener(readystatechange, arguments.callee); console.log(Content loaded); } });当 readystatechange 事件触发时这段代码会检测 document.readyState 属性以确定当前是不是交互或完成状态。如果是则移除事件处理程序以保证其他阶段不再执行。注意因为这里的事件处理程序是匿名函数所以使用了 arguments.callee 作为函数指针。然后又打印出一条表示内容已加载的消息。这样的逻辑可以保证尽可能接近使用 DOMContentLoaded 事件的效果。注意使用 readystatechange 只能尽量模拟 DOMContentLoaded但做不到分毫不差。load 事件和 readystatechange 事件发生的顺序在不同页面中是不一样的。 pageshow 与 pagehide 事件 Firefox 和 Opera 开发了一个名为往返缓存bfcacheback-forward cache的功能此功能旨在使用浏览器“前进”和“后退”按钮时加快页面之间的切换。这个缓存不仅存储页面数据也存储 DOM 和JavaScript 状态实际上是把整个页面都保存在内存里。如果页面在缓存中那么导航到这个页面时就不会触发 load 事件。通常这不会导致什么问题因为整个页面状态都被保存起来了。不过Firefx决定提供一些事件把往返缓存的行为暴露出来。第一个事件是 pageshow其会在页面显示时触发无论是否来自往返缓存。在新加载的页面上pageshow 会在 load 事件之后触发在来自往返缓存的页面上pageshow 会在页面状态完全恢复后触发。注意虽然这个事件的目标是 document但事件处理程序必须添加到 window 上。下面的例子展示了追踪这些事件的代码(function() { let showCount 0; window.addEventListener(load, () { console.log(Load fired); }); window.addEventListener(pageshow, () { showCount; console.log(Show has been fired ${showCount} times.); }); })();这个例子使用了私有作用域来保证 showCount 变量不进入全局作用域。在页面首次加载时showCount 的值为 0。之后每次触发 pageshow 事件showCount 都会加 1 并输出消息。如果从包含以上代码的页面跳走然后又点击“后退”按钮返回以恢复它就能够每次都看到 showCount 递增的值。这是因为变量的状态连同整个页面状态都保存在了内存中导航回来后可以恢复。如果是点击了浏览器的“刷新”按钮则 showCount 的值会重置为 0因为页面会重新加载。除了常用的属性pageshow 的 event 对象中还包含一个名为 persisted 的属性。这个属性是一个布尔值如果页面存储在了往返缓存中就是 true否则就是 false。可以像下面这样在事件处理程序中检测这个属性(function() { let showCount 0; window.addEventListener(load, () { console.log(Load fired); }); window.addEventListener(pageshow, () { showCount; console.log(Show has been fired ${showCount} times., Persisted? ${event.persisted}); }); })();通过检测 persisted 属性可以根据页面是否取自往返缓存而决定是否采取不同的操作。与 pageshow 对应的事件是 pagehide这个事件会在页面从浏览器中卸载后在 unload 事件之前触发。与 pageshow 事件一样pagehide 事件同样是在 document 上触发但事件处理程序必须被添加到 window。event 对象中同样包含 persisted 属性但用法稍有不同。比如以下代码检测了event.persisted 属性window.addEventListener(pagehide, (event) { console.log(Hiding. Persisted? event.persisted); });这样当 pagehide 事件触发时也许可以根据 persisted 属性的值来采取一些不同的操作。对pageshow 事件来说persisted 为 true 表示页面是从往返缓存中加载的而对 pagehide 事件来说persisted 为 true 表示页面在卸载之后会被保存在往返缓存中。因此第一次触发 pageshow 事件时 persisted 始终是 false而第一次触发 pagehide 事件时 persisted 始终是 true除非页面不符合使用往返缓存的条件。注意注册了 onunload 事件处理程序即使是空函数的页面会自动排除在往返缓存之外。这是因为 onunload 事件典型的使用场景是撤销 onload 事件发生时所做的事情如果使用往返缓存则下一次页面显示时就不会触发 onload 事件而这可能导致页面无法使用。 hashchange 事件 HTML5 增加了 hashchange 事件用于在 URL 散列值URL 最后#后面的部分发生变化时通知开发者。这是因为开发者经常在 Ajax 应用程序中使用 URL 散列值存储状态信息或路由导航信息。onhashchange 事件处理程序必须添加给 window每次 URL 散列值发生变化时会调用它。event对象有两个新属性oldURL 和 newURL。这两个属性分别保存变化前后的 URL而且是包含散列值的完整 URL。下面的例子展示了如何获取变化前后的 URLwindow.addEventListener(hashchange, (event) { console.log(Old URL: ${event.oldURL}, New URL: ${event.newURL}); });如果想确定当前的散列值最好使用 location 对象window.addEventListener(hashchange, (event) { console.log(Current hash: ${location.hash}); });设备事件 随着智能手机和平板计算机的出现用户与浏览器交互的新方式应运而生。为此一批新事件被发明了出来。设备事件可以用于确定用户使用设备的方式。W3C 在 2011 年就开始起草一份新规范用于定义新设备及设备相关的事件。 orientationchange 事件 苹果公司在移动 Safari 浏览器上创造了 orientationchange 事件以方便开发者判断用户的设备是处于垂直模式还是水平模式。移动 Safari 在 window 上暴露了 window.orientation 属性它有以下 3 种值之一0 表示垂直模式90 表示左转水平模式主屏幕键在右侧–90 表示右转水平模式主屏幕键在左。虽然相关文档也提及设备倒置后的值为 180但设备本身至今还不支持。每当用户旋转设备改变了模式就会触发 orientationchange 事件。但 event 对象上没有暴露任何有用的信息这是因为相关信息都可以从 window.orientation 属性中获取。以下是这个事件典型的用法window.addEventListener(load, (event) { let div document.getElementById(myDiv); div.innerHTML Current orientation is window.orientation; window.addEventListener(orientationchange, (event) { div.innerHTML Current orientation is window.orientation; }); });这个例子会在 load 事件触发时显示设备初始的朝向。然后又指定了 orientationchange 事件处理程序。此后只要这个事件触发页面就会更新以显示新的朝向信息。 所有 iOS 设备都支持 orientationchange 事件和 window.orientation 属性。注意因为 orientationchange 事件被认为是 window 事件所以也可以通过给body元素添加 onorientationchange 属性来指定事件处理程序。 deviceorientation 事件 deviceorientation 是DeviceOrientationEvent规范定义的事件。如果可以获取设备的加速计信息而且数据发生了变化这个事件就会在 window 上触发。要注意的是deviceorientation 事件只反映设备在空间中的朝向而不涉及移动相关的信息。设备本身处于 3D 空间即拥有 x 轴、y 轴和 z 轴的坐标系中。如果把设备静止放在水平的表面上那么三轴的值均为 0其中x 轴方向为从设备左侧到右侧y 轴方向为从设备底部到上部z 轴方向为从设备背面到正面。当 deviceorientation 触发时event 对象中会包含各个轴相对于设备静置时坐标值的变化主要是以下 5 个属性。 alpha0~360 范围内的浮点值表示围绕 z 轴旋转时 y 轴的度数左右转。beta–180~180 范围内的浮点值表示围绕 x 轴旋转时 z 轴的度数前后转。gamma–90~90 范围内的浮点值表示围绕 y 轴旋转时 z 轴的度数扭转。absolute布尔值表示设备是否返回绝对值。compassCalibrated布尔值表示设备的指南针是否正确校准。 下面是一个输出 alpha、beta 和 gamma 值的简单例子window.addEventListener(deviceorientation, (event) { let output document.getElementById(output); output.innerHTML Alpha${event.alpha}, Beta${event.beta}, Gamma${event.gamma}br; });基于这些信息可以随着设备朝向的变化重新组织或修改屏幕上显示的元素。例如以下代码会随着朝向变化旋转一个元素window.addEventListener(deviceorientation, (event) { let arrow document.getElementById(arrow); arrow.style.webkitTransform rotate(${Math.round(event.alpha)}deg); });这个例子只适用于移动 WebKit 浏览器因为使用的是专有的 webkitTransform 属性CSS 标准的 transform 属性的临时版本。“箭头”arrow元素会随着 event.alpha 值的变化而变化呈现出指南针的样子。这里给 CSS3 旋转变形函数传入了四舍五入后的值以确保平顺。 devicemotion 事件 DeviceOrientationEvent 规范也定义了 devicemotion 事件。这个事件用于提示设备实际上在移动而不仅仅是改变了朝向。例如devicemotion 事件可以用来确定设备正在掉落或者正拿在一个行走的人手里。当 devicemotion 事件触发时event 对象中包含如下额外的属性。 acceleration对象包含 x、y 和 z 属性反映不考虑重力情况下各个维度的加速信息。accelerationIncludingGravity对象包含 x、y 和 z 属性反映各个维度的加速信息包含 z 轴自然重力加速度。interval毫秒距离下次触发 devicemotion 事件的时间。此值在事件之间应为常量。rotationRate对象包含 alpha、beta 和 gamma 属性表示设备朝向。 如果无法提供 acceleration、accelerationIncludingGravity 和 rotationRate 信息则属性值为 null。为此在使用这些属性前必须先检测它们的值是否为 null。比如window.addEventListener(devicemotion, (event) { let output document.getElementById(output); if (event.rotationRate ! null) { output.innerHTML Alpha${event.rotationRate.alpha} Beta${event.rotationRate.beta} Gamma${event.rotationRate.gamma}; } });触摸及手势事件 Safari 为 iOS 定制了一些专有事件以方便开发者。因为 iOS 设备没有鼠标和键盘所以常规的鼠标和键盘事件不足以创建具有完整交互能力的网页。同时WebKit 也为 Android 定制了很多专有事件成为了事实标准并被纳入 W3C 的 Touch Events 规范。本节介绍的事件只适用于触屏设备。 触摸事件 iPhone 3G 发布时iOS 2.0 内置了新版本的 Safari。这个新的移动 Safari 支持一些与触摸交互有关的新事件。后来的 Android 浏览器也实现了同样的事件。当手指放在屏幕上、在屏幕上滑动或从屏幕移开时触摸事件即会触发。触摸事件有如下几种。 touchstart手指放到屏幕上时触发即使有一个手指已经放在了屏幕上。touchmove手指在屏幕上滑动时连续触发。在这个事件中调用 preventDefault()可以阻止滚动。touchend手指从屏幕上移开时触发。touchcancel系统停止跟踪触摸时触发。文档中并未明确什么情况下停止跟踪。 这些事件都会冒泡也都可以被取消。尽管触摸事件不属于 DOM 规范但浏览器仍然以兼容 DOM的方式实现了它们。因此每个触摸事件的 event 对象都提供了鼠标事件的公共属性bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey 和 metaKey。除了这些公共的 DOM 属性触摸事件还提供了以下 3 个属性用于跟踪触点。 touchesTouch 对象的数组表示当前屏幕上的每个触点。targetTouchesTouch 对象的数组表示特定于事件目标的触点。changedTouchesTouch 对象的数组表示自上次用户动作之后变化的触点。 每个 Touch 对象都包含下列属性。 clientX触点在视口中的 x 坐标。clientY触点在视口中的 y 坐标。identifier触点 ID。pageX触点在页面上的 x 坐标。pageY触点在页面上的 y 坐标。screenX触点在屏幕上的 x 坐标。screenY触点在屏幕上的 y 坐标。target触摸事件的事件目标。 这些属性可用于追踪屏幕上的触摸轨迹。例如function handleTouchEvent(event) { // 只针对一个触点if (event.touches.length 1) { let output document.getElementById(output); switch(event.type) { case touchstart: output.innerHTML brTouch started: (${event.touches[0].clientX} ${event.touches[0].clientY}); break; case touchend: output.innerHTML brTouch ended: (${event.changedTouches[0].clientX} ${event.changedTouches[0].clientY}); break; case touchmove: event.preventDefault(); // 阻止滚动output.innerHTML brTouch moved: (${event.changedTouches[0].clientX} ${event.changedTouches[0].clientY}); break; } } } document.addEventListener(touchstart, handleTouchEvent); document.addEventListener(touchend, handleTouchEvent); document.addEventListener(touchmove, handleTouchEvent);以上代码会追踪屏幕上的一个触点。为简单起见代码只会在屏幕有一个触点时输出信息。在touchstart 事件触发时触点的位置信息会输出到 output 元素中。在 touchmove 事件触发时会取消默认行为以阻止滚动移动触点通常会滚动页面并输出变化的触点信息。在 touchend 事件触发时会输出触点最后的信息。注意touchend 事件触发时 touches 集合中什么也没有这是因为没有滚动的触点了。此时必须使用 changedTouches 集合。这些事件会在文档的所有元素上触发因此可以分别控制页面的不同部分。当手指点触屏幕上的元素时依次会发生如下事件包括鼠标事件 (1) touchstart(2) mouseover(3) mousemove1 次(4) mousedown(5) mouseup(6) click(7) touchend 手势事件 iOS 2.0 中的 Safari 还增加了一种手势事件。手势事件会在两个手指触碰屏幕且相对距离或旋转角度变化时触发。手势事件有以下 3 种。 gesturestart一个手指已经放在屏幕上再把另一个手指放到屏幕上时触发。gesturechange任何一个手指在屏幕上的位置发生变化时触发。gestureend其中一个手指离开屏幕时触发。 只有在两个手指同时接触事件接收者时这些事件才会触发。在一个元素上设置事件处理程序意味着两个手指必须都在元素边界以内才能触发手势事件这个元素就是事件目标。因为这些事件会冒泡所以也可以把事件处理程序放到文档级别从而可以处理所有手势事件。使用这种方式时事件的目标就是两个手指均位于其边界内的元素。触摸事件和手势事件存在一定的关系。当一个手指放在屏幕上时会触发 touchstart 事件。当另一个手指放到屏幕上时gesturestart 事件会首先触发然后紧接着触发这个手指的 touchstart事件。如果两个手指或其中一个手指移动则会触发 gesturechange 事件。只要其中一个手指离开屏幕就会触发 gestureend 事件紧接着触发该手指的 touchend 事件。与触摸事件类似每个手势事件的 event 对象都包含所有标准的鼠标事件属性bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey 和 metaKey。新增的两个 event 对象属性是 rotation 和 scale。rotation 属性表示手指变化旋转的度数负值表示逆时针旋转正值表示顺时针旋转从 0 开始。scale 属性表示两指之间距离变化对捏的程度。开始时为 1然后随着距离增大或缩小相应地增大或缩小。可以像下面这样使用手势事件的属性function handleGestureEvent(event) { let output document.getElementById(output); switch(event.type) { case gesturestart: output.innerHTML Gesture started: rotation${event.rotation}, scale${event.scale};break; case gestureend: output.innerHTML Gesture ended: rotation${event.rotation}, scale${event.scale}; break; case gesturechange: output.innerHTML Gesture changed: rotation${event.rotation}, scale${event.scale}; break; } } document.addEventListener(gesturestart, handleGestureEvent, false); document.addEventListener(gestureend, handleGestureEvent, false); document.addEventListener(gesturechange, handleGestureEvent, false);与触摸事件的例子一样以上代码简单地将每个事件对应到一个处理函数然后输出每个事件的信息。注意触摸事件也会返回 rotation 和 scale 属性但只在两个手指触碰屏幕时才会变化。一般来说使用两个手指的手势事件比考虑所有交互的触摸事件使用起来更容易一些。 事件参考 本节给出了 DOM 规范、HTML5 规范以及概述事件行为的其他当前已发布规范中定义的所有浏览器事件。这些事件按照 API 和/或规范分类。注意只包含带厂商前缀事件的规范不在本参考中。读者注笔记中不带上详细事件列表详看《avaScript高级程序设计 (第4版) 》P534。 内存与性能 因为事件处理程序在现代 Web 应用中可以实现交互所以很多开发者会错误地在页面中大量使用它们。在创建 GUI 的语言如 C#中通常会给 GUI 上的每个按钮设置一个 onclick 事件处理程序。这样做不会有什么性能损耗。在 JavaScript 中页面中事件处理程序的数量与页面整体性能直接相关。原因有很多。首先每个函数都是对象都占用内存空间对象越多性能越差。其次为指定事件处理程序所需访问 DOM 的次数会先期造成整个页面交互的延迟。只要在使用事件处理程序时多注意一些方法就可以改善页面性能。 事件委托 “过多事件处理程序”的解决方案是使用事件委托。事件委托利用事件冒泡可以只使用一个事件处理程序来管理一种类型的事件。例如click 事件冒泡到 document。这意味着可以为整个页面指定一个 onclick 事件处理程序而不用为每个可点击元素分别指定事件处理程序。比如有以下 HTMLul idmyLinks li idgoSomewhereGo somewhere/li li iddoSomethingDo something/li li idsayHiSay hi/li /ul这里的 HTML 包含 3 个列表项在被点击时应该执行某个操作。对此通常的做法是像这样指定 3个事件处理程序let item1 document.getElementById(goSomewhere); let item2 document.getElementById(doSomething); let item3 document.getElementById(sayHi); item1.addEventListener(click, (event) { location.href http:// www.wrox.com; }); item2.addEventListener(click, (event) { document.title I changed the documents title; }); item3.addEventListener(click, (event) { console.log(hi); });如果对页面中所有需要使用 onclick 事件处理程序的元素都如法炮制结果就会出现大片雷同的只为指定事件处理程序的代码。使用事件委托只要给所有元素共同的祖先节点添加一个事件处理程序就可以解决问题。比如let list document.getElementById(myLinks); list.addEventListener(click, (event) { let target event.target; switch(target.id) { case doSomething: document.title I changed the documents title; break; case goSomewhere: location.href http:// www.wrox.com; break; case sayHi: console.log(hi); break; } });这里只给ul idmyLinks元素添加了一个 onclick 事件处理程序。因为所有列表项都是这个元素的后代所以它们的事件会向上冒泡最终都会由这个函数来处理。但事件目标是每个被点击的列表项只要检查 event 对象的 id 属性就可以确定然后再执行相应的操作即可。相对于前面不使用事件委托的代码这里的代码不会导致先期延迟因为只访问了一个 DOM 元素和添加了一个事件处理程序。结果对用户来说没有区别但这种方式占用内存更少。所有使用按钮的事件大多数鼠标事件和键盘事件都适用于这个解决方案。只要可行就应该考虑只给 document 添加一个事件处理程序通过它处理页面中所有某种类型的事件。相对于之前的技术事件委托具有如下优点。 document 对象随时可用任何时候都可以给它添加事件处理程序不用等待 DOMContentLoaded或 load 事件。这意味着只要页面渲染出可点击的元素就可以无延迟地起作用。节省花在设置页面事件处理程序上的时间。只指定一个事件处理程序既可以节省 DOM 引用也可以节省时间。减少整个页面所需的内存提升整体性能。 最适合使用事件委托的事件包括click、mousedown、mouseup、keydown 和 keypress。mouseover 和 mouseout 事件冒泡但很难适当处理且经常需要计算元素位置因为 mouseout 会在光标从一个元素移动到它的一个后代节点以及移出元素之外时触发。 删除事件处理程序 把事件处理程序指定给元素后在浏览器代码和负责页面交互的 JavaScript 代码之间就建立了联系。这种联系建立得越多页面性能就越差。除了通过事件委托来限制这种连接之外还应该及时删除不用的事件处理程序。很多 Web 应用性能不佳都是由于无用的事件处理程序长驻内存导致的。导致这个问题的原因主要有两个。第一个是删除带有事件处理程序的元素。比如通过真正的 DOM方法 removeChild()或 replaceChild()删除节点。最常见的还是使用 innerHTML 整体替换页面的某一部分。这时候被 innerHTML 删除的元素上如果有事件处理程序就不会被垃圾收集程序正常清理。比如下面的例子div idmyDiv input typebutton valueClick Me idmyBtn /div script typetext/javascript let btn document.getElementById(myBtn); btn.onclick function() { // 执行操作document.getElementById(myDiv).innerHTML Processing...; // 不好}; /script这里的按钮在div元素中。单击按钮会将自己删除并替换为一条消息以阻止双击发生。这是很多网站上常见的做法。问题在于按钮被删除之后仍然关联着一个事件处理程序。在div元素上设置 innerHTML 会完全删除按钮但事件处理程序仍然挂在按钮上面。某些浏览器特别是 IE8 及更早版本在这时候就会有问题了。很有可能元素的引用和事件处理程序的引用都会残留在内存中。如果知道某个元素会被删除那么最好在删除它之前手工删除它的事件处理程序比如div idmyDiv input typebutton valueClick Me idmyBtn /div script typetext/javascript let btn document.getElementById(myBtn); btn.onclick function() { // 执行操作btn.onclick null; // 删除事件处理程序document.getElementById(myDiv).innerHTML Processing...; }; /script在这个重写后的例子中设置div元素的 innerHTML 属性之前按钮的事件处理程序先被删除了。这样就可以确保内存被回收按钮也可以安全地从 DOM 中删掉。但也要注意在事件处理程序中删除按钮会阻止事件冒泡。只有事件目标仍然存在于文档中时事件才会冒泡。注意事件委托也有助于解决这种问题。如果提前知道页面某一部分会被使用innerHTML删除就不要直接给该部分中的元素添加事件处理程序了。把事件处理程序添加到更高层级的节点上同样可以处理该区域的事件。另一个可能导致内存中残留引用的问题是页面卸载。同样IE8 及更早版本在这种情况下有很多问题不过好像所有浏览器都会受这个问题影响。如果在页面卸载后事件处理程序没有被清理则它们仍然会残留在内存中。之后浏览器每次加载和卸载页面比如通过前进、后退或刷新内存中残留对象的数量都会增加这是因为事件处理程序不会被回收。一般来说最好在 onunload 事件处理程序中趁页面尚未卸载先删除所有事件处理程序。这时候也能体现使用事件委托的优势因为事件处理程序很少所以很容易记住要删除哪些。关于卸载页面时的清理可以记住一点onload 事件处理程序中做了什么最好在 onunload 事件处理程序中恢复。注意在页面中使用 onunload 事件处理程序意味着页面不会被保存在往返缓存bfcache中。如果这对应用很重要可以考虑只在 IE 中使用 onunload 来删除事件处理程序。 模拟事件 事件就是为了表示网页中某个有意义的时刻。通常事件都是由用户交互或浏览器功能触发。事实上可能很少有人知道可以通过 JavaScript 在任何时候触发任意事件而这些事件会被当成浏览器创建的事件。这意味着同样会有事件冒泡因而也会触发相应的事件处理程序。这种能力在测试 Web 应用时特别有用。DOM3 规范指明了模拟特定类型事件的方式。IE8 及更早版本也有自己模拟事件的方式。 DOM 事件模拟 任何时候都可以使用 document.createEvent()方法创建一个 event 对象。这个方法接收一个参数此参数是一个表示要创建事件类型的字符串。在 DOM2 中所有这些字符串都是英文复数形式但在 DOM3 中又把它们改成了英文单数形式。可用的字符串值是以下值之一。 “UIEvents”DOM3 中是UIEvent通用用户界面事件鼠标事件和键盘事件都继承自这个事件。“MouseEvents”DOM3 中是MouseEvent通用鼠标事件。“HTMLEvents”DOM3 中没有通用 HTML 事件HTML 事件已经分散到了其他事件大类中。 注意键盘事件不是在 DOM2 Events 中规定的而是后来在 DOM3 Events 中增加的。创建 event 对象之后需要使用事件相关的信息来初始化。每种类型的 event 对象都有特定的方法可以使用相应数据来完成初始化。方法的名字并不相同这取决于调用 createEvent()时传入的参数。事件模拟的最后一步是触发事件。为此要使用 dispatchEvent()方法这个方法存在于所有支持事件的 DOM 节点之上。dispatchEvent()方法接收一个参数即表示要触发事件的 event 对象。调用 dispatchEvent()方法之后事件就“转正”了接着便冒泡并触发事件处理程序执行。 模拟鼠标事件 模拟鼠标事件需要先创建一个新的鼠标 event 对象然后再使用必要的信息对其进行初始化。要创建鼠标 event 对象可以调用 createEvent()方法并传入MouseEvents参数。这样就会返回一个 event 对象这个对象有一个 initMouseEvent()方法用于为新对象指定鼠标的特定信息。initMouseEvent()方法接收 15 个参数分别对应鼠标事件会暴露的属性。这些参数列举如下。 type字符串要触发的事件类型如click。bubbles布尔值表示事件是否冒泡。为精确模拟鼠标事件应该设置为 true。cancelable布尔值表示事件是否可以取消。为精确模拟鼠标事件应该设置为 true。viewAbstractView与事件关联的视图。基本上始终是 document.defaultView。detail整数关于事件的额外信息。只被事件处理程序使用通常为 0。screenX整数事件相对于屏幕的 x 坐标。screenY整数事件相对于屏幕的 y 坐标。clientX整数事件相对于视口的 x 坐标。clientY整数事件相对于视口的 y 坐标。ctrlkey布尔值表示是否按下了 Ctrl 键。默认为 false。altkey布尔值表示是否按下了 Alt 键。默认为 false。shiftkey布尔值表示是否按下了 Shift 键。默认为 false。metakey布尔值表示是否按下了 Meta 键。默认为 false。button整数表示按下了哪个按钮。默认为 0。relatedTarget对象与事件相关的对象。只在模拟 mouseover 和 mouseout 时使用。 显然initMouseEvent()方法的这些参数与鼠标事件的 event 对象属性是一一对应的。前 4 个参数是正确模拟事件唯一重要的几个参数这是因为它们是浏览器要用的其他参数则是事件处理程序要用的。event 对象的 target 属性会自动设置为调用 dispatchEvent()方法时传入的节点。下面来看一个使用默认值模拟单击事件的例子let btn document.getElementById(myBtn); // 创建 event 对象 let event document.createEvent(MouseEvents); // 初始化 event 对象 event.initMouseEvent(click, true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); // 触发事件 btn.dispatchEvent(event); 所有鼠标事件包括 dblclick 都可以像这样在 DOM 合规的浏览器中模拟出来。 模拟键盘事件 如前所述DOM2 Events 中没有定义键盘事件因此模拟键盘事件并不直观。键盘事件曾在 DOM2 Events 的草案中提到过但最终成为推荐标准前又被删掉了。要注意的是DOM3 Events 中定义的键盘事件与 DOM2 Events 草案最初定义的键盘事件差别很大。在 DOM3 中创建键盘事件的方式是给 createEvent()方法传入参数KeyboardEvent。这样会返回一个 event 对象这个对象有一个 initKeyboardEvent()方法。这个方法接收以下参数。 type字符串要触发的事件类型如keydown。bubbles布尔值表示事件是否冒泡。为精确模拟键盘事件应该设置为 true。cancelable布尔值表示事件是否可以取消。为精确模拟键盘事件应该设置为 true。viewAbstractView与事件关联的视图。基本上始终是 document.defaultView。key字符串按下按键的字符串代码。location整数按下按键的位置。0 表示默认键1 表示左边2 表示右边3 表示数字键盘4 表示移动设备虚拟键盘5 表示游戏手柄。modifiers字符串空格分隔的修饰键列表如Shift。repeat整数连续按了这个键多少次。 注意DOM3 Events 废弃了 keypress 事件因此只能通过上述方式模拟 keydown 和 keyup 事件let textbox document.getElementById(myTextbox), event; // 按照 DOM3 的方式创建 event 对象 if (document.implementation.hasFeature(KeyboardEvents, 3.0)) { event document.createEvent(KeyboardEvent); // 初始化 event 对象event.initKeyboardEvent(keydown, true, true, document.defaultView, a, 0, Shift, 0); } // 触发事件 textbox.dispatchEvent(event);这个例子模拟了同时按住 Shift 键和键盘上 A 键的 keydown 事件。在使用 document.createEvent(“KeyboardEvent”)之前最好检测一下浏览器对 DOM3 键盘事件的支持情况其他浏览器会返回非标准的 KeyboardEvent 对象。Firefox 允许给 createEvent()传入KeyEvents来创建键盘事件。这时候返回的 event 对象包含的方法叫 initKeyEvent()此方法接收以下 10 个参数。 type字符串要触发的事件类型如keydown。bubbles布尔值表示事件是否冒泡。为精确模拟键盘事件应该设置为 true。cancelable布尔值表示事件是否可以取消。为精确模拟键盘事件应该设置为 true。viewAbstractView与事件关联的视图基本上始终是 document.defaultView。ctrlkey布尔值表示是否按下了 Ctrl 键。默认为 false。altkey布尔值表示是否按下了 Alt 键。默认为 false。shiftkey布尔值表示是否按下了 Shift 键。默认为 false。metakey布尔值表示是否按下了 Meta 键。默认为 false。keyCode整数表示按下或释放键的键码。在 keydown 和 keyup 中使用。默认为 0。charCode整数表示按下键对应字符的 ASCII 编码。在 keypress 中使用。默认为 0。 键盘事件也可以通过调用 dispatchEvent()并传入 event 对象来触发比如// 仅适用于 Firefox let textbox document.getElementById(myTextbox); // 创建 event 对象 let event document.createEvent(KeyEvents); // 初始化 event 对象 event.initKeyEvent(keydown, true, true, document.defaultView, false, false, true, false, 65, 65); // 触发事件 textbox.dispatchEvent(event);这个例子模拟了同时按住 Shift 键和键盘上 A 键的 keydown 事件。同样也可以像这样模拟 keyup和 keypress 事件。对于其他浏览器需要创建一个通用的事件并为其指定特定于键盘的信息如下面的例子所示let textbox document.getElementById(myTextbox); // 创建 event 对象 let event document.createEvent(Events); // 初始化 event 对象 event.initEvent(type, bubbles, cancelable); event.view document.defaultView; event.altKey false; event.ctrlKey false; event.shiftKey false; event.metaKey false; event.keyCode 65; event.charCode 65; // 触发事件 textbox.dispatchEvent(event);以上代码创建了一个通用事件然后使用 initEvent()方法初始化接着又为它指定了键盘事件信息。这里必须使用通用事件而不是用户界面事件因为用户界面事件不允许直接给 event 对象添加属性Safari 例外。像这样模拟一个事件虽然会触发键盘事件但文本框中不会输入任何文本因为它并不能准确模拟键盘事件。 模拟其他事件 鼠标事件和键盘事件是浏览器中最常见的模拟对象。不过有时候可能也需要模拟 HTML 事件。模拟 HTML 事件要调用 createEvent()方法并传入HTMLEvents然后再使用返回对象的initEvent()方法来初始化let event document.createEvent(HTMLEvents); event.initEvent(focus, true, false); target.dispatchEvent(event);这个例子模拟了在给定目标上触发 focus 事件。其他 HTML 事件也可以像这样来模拟。注意HTML 事件在浏览器中很少使用因为它们用处有限。 自定义 DOM 事件 DOM3 增加了自定义事件的类型。自定义事件不会触发原生 DOM 事件但可以让开发者定义自己的事件。要创建自定义事件需要调用 createEvent(“CustomEvent”) 。返回的对象包含initCustomEvent()方法该方法接收以下 4 个参数。 type字符串要触发的事件类型如myevent。bubbles布尔值表示事件是否冒泡。cancelable布尔值表示事件是否可以取消。detail对象任意值。作为 event 对象的 detail 属性。 自定义事件可以像其他事件一样在 DOM 中派发比如let div document.getElementById(myDiv), event; div.addEventListener(myevent, (event) { console.log(DIV: event.detail); }); document.addEventListener(myevent, (event) { console.log(DOCUMENT: event.detail); }); if (document.implementation.hasFeature(CustomEvents, 3.0)) { event document.createEvent(CustomEvent); event.initCustomEvent(myevent, true, false, Hello world!); div.dispatchEvent(event); }这个例子创建了一个名为myevent的冒泡事件。event 对象的 detail 属性就是一个简单的字符串div元素和 document 都为这个事件注册了事件处理程序。因为使用 initCustomEvent()初始化时将事件指定为可以冒泡所以浏览器会负责把事件冒泡到 document。 IE 事件模拟 在 IE8 及更早版本中模拟事件的过程与 DOM 方式类似创建 event 对象指定相应信息然后使用这个对象触发。当然IE 实现每一步的方式都不一样。首先要使用 document 对象的 createEventObject()方法来创建 event 对象。与 DOM 不同这个方法不接收参数返回一个通用 event 对象。然后可以手工给返回的对象指定希望该对象具备的所有属性。没有初始化方法。最后一步是在事件目标上调用 fireEvent()方法这个方法接收两个参数事件处理程序的名字和 event 对象。调用 fireEvent()时srcElement 和 type 属性会自动指派到 event 对象其他所有属性必须手工指定。这意味着 IE 支持的所有事件都可以通过相同的方式来模拟。例如下面的代码在一个按钮上模拟了 click 事件var btn document.getElementById(myBtn); // 创建 event 对象 var event document.createEventObject(); /// 初始化 event 对象 event.screenX 100; event.screenY 0; event.clientX 0; event.clientY 0; event.ctrlKey false; event.altKey false; event.shiftKey false; event.button 0; // 触发事件 btn.fireEvent(onclick, event);这个例子先创建 event 对象然后用相关信息对其进行了初始化。注意这里可以指定任何属性包括 IE8 及更早版本不支持的属性。这些属性的值对于事件来说并不重要因为只有事件处理程序才会使用它们。同样的方式也可以用来模拟 keypress 事件如下面的例子所示var textbox document.getElementById(myTextbox); // 创建 event 对象 var event document.createEventObject(); // 初始化 event 对象 event.altKey false; event.ctrlKey false; event.shiftKey false; event.keyCode 65; // 触发事件 textbox.fireEvent(onkeypress, event);由于鼠标事件、键盘事件或其他事件的 event 对象并没有区别因此使用通用的 event 对象可以触发任何类型的事件。注意与 DOM 方式模拟键盘事件一样这里模拟的 keypress 虽然会触发但文本框中也不会出现字符。 小结 事件是 JavaScript 与网页结合的主要方式。最常见的事件是在 DOM3 Events 规范或 HTML5 中定义的。虽然基本的事件都有规范定义但很多浏览器在规范之外实现了自己专有的事件以方便开发者更好地满足用户交互需求其中一些专有事件直接与特殊的设备相关。围绕着使用事件需要考虑内存与性能问题。例如 最好限制一个页面中事件处理程序的数量因为它们会占用过多内存导致页面响应缓慢利用事件冒泡事件委托可以解决限制事件处理程序数量的问题最好在页面卸载之前删除所有事件处理程序。 使用 JavaScript 也可以在浏览器中模拟事件。DOM2 Events 和 DOM3 Events 规范提供了模拟方法可以模拟所有原生 DOM 事件。键盘事件一定程度上也是可以模拟的有时候需要组合其他技术。IE8及更早版本也支持事件模拟只是接口与 DOM 方式不同。事件是 JavaScript 中最重要的主题之一理解事件的原理及其对性能的影响非常重要。
文章转载自:
http://www.morning.zzjpy.cn.gov.cn.zzjpy.cn
http://www.morning.ctxt.cn.gov.cn.ctxt.cn
http://www.morning.tfkqc.cn.gov.cn.tfkqc.cn
http://www.morning.ynjhk.cn.gov.cn.ynjhk.cn
http://www.morning.hqwtm.cn.gov.cn.hqwtm.cn
http://www.morning.jzykq.cn.gov.cn.jzykq.cn
http://www.morning.lrplh.cn.gov.cn.lrplh.cn
http://www.morning.zrmxp.cn.gov.cn.zrmxp.cn
http://www.morning.rwbh.cn.gov.cn.rwbh.cn
http://www.morning.npqps.cn.gov.cn.npqps.cn
http://www.morning.pjtnk.cn.gov.cn.pjtnk.cn
http://www.morning.qmkyp.cn.gov.cn.qmkyp.cn
http://www.morning.madamli.com.gov.cn.madamli.com
http://www.morning.mnbgx.cn.gov.cn.mnbgx.cn
http://www.morning.cqyhdy.cn.gov.cn.cqyhdy.cn
http://www.morning.xbnkm.cn.gov.cn.xbnkm.cn
http://www.morning.bnylg.cn.gov.cn.bnylg.cn
http://www.morning.ywtbk.cn.gov.cn.ywtbk.cn
http://www.morning.rmqlf.cn.gov.cn.rmqlf.cn
http://www.morning.rbmm.cn.gov.cn.rbmm.cn
http://www.morning.mkyxp.cn.gov.cn.mkyxp.cn
http://www.morning.pnjsl.cn.gov.cn.pnjsl.cn
http://www.morning.qbwyd.cn.gov.cn.qbwyd.cn
http://www.morning.wzknt.cn.gov.cn.wzknt.cn
http://www.morning.ksqzd.cn.gov.cn.ksqzd.cn
http://www.morning.rfljb.cn.gov.cn.rfljb.cn
http://www.morning.gjwkl.cn.gov.cn.gjwkl.cn
http://www.morning.nrqnj.cn.gov.cn.nrqnj.cn
http://www.morning.ntqjh.cn.gov.cn.ntqjh.cn
http://www.morning.bnqcm.cn.gov.cn.bnqcm.cn
http://www.morning.bpmdq.cn.gov.cn.bpmdq.cn
http://www.morning.kzslk.cn.gov.cn.kzslk.cn
http://www.morning.yllym.cn.gov.cn.yllym.cn
http://www.morning.xphcg.cn.gov.cn.xphcg.cn
http://www.morning.qpsdq.cn.gov.cn.qpsdq.cn
http://www.morning.flqkp.cn.gov.cn.flqkp.cn
http://www.morning.qtzwh.cn.gov.cn.qtzwh.cn
http://www.morning.mhnxs.cn.gov.cn.mhnxs.cn
http://www.morning.tsycr.cn.gov.cn.tsycr.cn
http://www.morning.vuref.cn.gov.cn.vuref.cn
http://www.morning.dskzr.cn.gov.cn.dskzr.cn
http://www.morning.djlxz.cn.gov.cn.djlxz.cn
http://www.morning.plfy.cn.gov.cn.plfy.cn
http://www.morning.wyrkp.cn.gov.cn.wyrkp.cn
http://www.morning.bcnsl.cn.gov.cn.bcnsl.cn
http://www.morning.hwxxh.cn.gov.cn.hwxxh.cn
http://www.morning.bgpb.cn.gov.cn.bgpb.cn
http://www.morning.mehrim.com.gov.cn.mehrim.com
http://www.morning.fysdt.cn.gov.cn.fysdt.cn
http://www.morning.fmjzl.cn.gov.cn.fmjzl.cn
http://www.morning.clybn.cn.gov.cn.clybn.cn
http://www.morning.qggxt.cn.gov.cn.qggxt.cn
http://www.morning.sh-wj.com.cn.gov.cn.sh-wj.com.cn
http://www.morning.skrcn.cn.gov.cn.skrcn.cn
http://www.morning.jqpq.cn.gov.cn.jqpq.cn
http://www.morning.pbmkh.cn.gov.cn.pbmkh.cn
http://www.morning.xsfg.cn.gov.cn.xsfg.cn
http://www.morning.nrmyj.cn.gov.cn.nrmyj.cn
http://www.morning.ldynr.cn.gov.cn.ldynr.cn
http://www.morning.lpcct.cn.gov.cn.lpcct.cn
http://www.morning.pjxw.cn.gov.cn.pjxw.cn
http://www.morning.rbnnq.cn.gov.cn.rbnnq.cn
http://www.morning.jwefry.cn.gov.cn.jwefry.cn
http://www.morning.nbhft.cn.gov.cn.nbhft.cn
http://www.morning.xxrgt.cn.gov.cn.xxrgt.cn
http://www.morning.mqss.cn.gov.cn.mqss.cn
http://www.morning.kjmws.cn.gov.cn.kjmws.cn
http://www.morning.xhftj.cn.gov.cn.xhftj.cn
http://www.morning.pjrql.cn.gov.cn.pjrql.cn
http://www.morning.fqklt.cn.gov.cn.fqklt.cn
http://www.morning.bsxws.cn.gov.cn.bsxws.cn
http://www.morning.hrpjx.cn.gov.cn.hrpjx.cn
http://www.morning.jjwt.cn.gov.cn.jjwt.cn
http://www.morning.qczpf.cn.gov.cn.qczpf.cn
http://www.morning.wljzr.cn.gov.cn.wljzr.cn
http://www.morning.rwdbz.cn.gov.cn.rwdbz.cn
http://www.morning.spqbp.cn.gov.cn.spqbp.cn
http://www.morning.nshhf.cn.gov.cn.nshhf.cn
http://www.morning.zmlbq.cn.gov.cn.zmlbq.cn
http://www.morning.brjq.cn.gov.cn.brjq.cn
http://www.tj-hxxt.cn/news/256940.html

相关文章:

  • 指定关键字 网站有更新就提醒杭州北京网站建设公司
  • 建设网站 翻译新开网页游戏开服表
  • 网站建设平台硬件要求wordpress国内主题
  • 静态网站seo怎么做情女照片做杯子网站
  • 顶做抱枕网站南沙营销网站建设
  • 网站设计的关键简述seo和sem的区别
  • 网站留言板漏洞医院可以做网站吗
  • 红杉网站建设网站优化公司哪家效果好
  • 剧院网站建设汤阴做网站
  • 网站首页的文字下拉怎么做当地网站建设问卷调查
  • 任经理 徐州网站建设订餐网站系统建设方案
  • 营销型网站和传统网站区别用户体验设计师证书
  • 唯品会网站建设建议手机网站建设行业现状
  • 天站网站建设网站建设投标评分标准
  • 广东网站建设找哪家杭州谷歌seo公司
  • 邢台网站网站建设产品设计考研学校排名
  • 英文网站seo 谷歌免费网站哪个好
  • 机关公文写作网站下载淘宝app免费下载安装
  • 电信做网站吗网络舆情处置公司
  • 网站建设与管理ppt模板智能营销系统
  • 网站开发课程设计培训销售型公司
  • 免费网站源码模板下载怎样入驻微信小程序
  • 网站建设实施计划包括哪些方面湖南湘源建设工程有限公司网站
  • 网站建设结束的售后服务电商平台设计方案
  • dedecms做手机网站软件工程职业生涯规划书
  • keywordspy网站做分析时光轴网站模板
  • 电子商务网站建设的核心网站前端工资
  • 网站开发成本最低多少钱wordpress 网站 注册
  • 网站站长工具网站手机优化显示
  • 电商网站是什么wordpress 邮件美化