1### Menu
第13章 事 件
13.2 事件处理程序
- 13.2.1 HTML事件处理程序
- 13.2.2 DOM0 级事件处理程序
- 13.2.3 DOM2 级事件处理程序
- 13.2.4 IE事件处理程序
- 13.2.5 跨浏览器的事件处理程序
13.3 事件对象
- 13.3.1 DOM中的事件对象
13.4 事件类型
- 13.4.1 UI事件
- 13.4.2 焦点事件
- 13.4.3 鼠标与滚轮事件
- 13.4.4 键盘与文本事件
- 13.4.6 变动事件
- 13.4.7 HTML5 事件
13.5 内存和性能
- 13.5.1 事件委托
- 13.5.2 移除事件处理程序
13.6 模拟事件
- 13.6.1 DOM中的事件模拟
第13章 事 件
13.2 事件处理程序
13.2.1 HTML事件处理程序
- 这个操作是通过指定 onclick 特性并将一些 JavaScript
代码作为它的值来定义的。由于这个值是 JavaScript,因此不能在其中使用未经转义的 HTML 语法字符,例如和号(&)、双引号("")、小于号(<)或大于号(>)。为了避免使用 HTML 实体,这里使用了单引号。如果想要使用双引号,那么就要将代码改写成如下所示:
- 在 HTML 中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本,如下面的例子所示
13.2.2 DOM0 级事件处理程序
- 使用 DOM0 级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在
元素的作用域中运行;换句话说,程序中的 this 引用当前元素。来看一个例子。
- 单击按钮,button的字体会变成红色,this就代表当前操作的元素;
- 也可以删除通过 DOM0 级方法指定的事件处理程序;
btn.onclick = null; //删除事件处理程序
13.2.3 DOM2 级事件处理程序
- “ DOM2 级事件” 定义了两个方法,用于处理指定和删除事件处理程序的操作: addEventListener()和 removeEventListener()。所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数(“时间名”,处理事件程序,一个布尔值),最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序(布尔值最好填false)。 代码如下:
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
- 使用 DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。
// 会按顺序触发,先alert“mybtn”,再alert“hello world”
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
btn.addEventListener("click", function(){
alert("Hello world!");
}, false);
- removeEventListener()
removeEventListener()中的事件处理程序函数必须与传入
addEventListener()中的相同,如果用匿名函数的话就无法移除;
有效例子如下:
var btn = document.getElementById("myBtn");
var handler = function(){
alert(this.id);
};
btn.addEventListener("click", handler, false);
//这里省略了其他代码
btn.removeEventListener("click", handler, false); //有效!
- 大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需
要,我们不建议在事件捕获阶段注册事件处理程序。
- 13.2.5 跨浏览器的事件处理程序
- 第一个要创建的方法是 addHandler(),它的职责是视情况分别使用 DOM0 级方法、 DOM2 级方法,这个方法属于一个名叫 EventUtil 的对象,本书将使用这个对象来处理浏览器间的差异。addHandler()方法接受 3 个参数:要操作的元素、事件名称和事件处理程序函数。
- 这两个方法首先都会检测传入的元素中是否存在 DOM2 级方法。如果存在 DOM2 级方法,则使用该方法:传入事件类型、事件处理程序函数和第三个参数 false(表示冒泡阶段)。注意,为了在 IE8 及更早版本中运行,此时的事件类型必须加上"on"前缀。
最后一种可能就是使用 DOM0 级方法(在现代浏览器中,应该不会执行这里的代码)。此时,我们使用的是方括号语法来将属性名指定为事件处理程序,或者将属性设置为 null。
var EventUtil = {
addHandler: function(element, type, handler){
if (element.addEventListener){
element.addEventListener(type, handler, false);
} else {
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler){
if (element.removeEventListener){
element.removeEventListener(type, handler, false);
} else {
element["on" + type] = null;
}
}
};
- 可以像下面这样使用 EventUtil 对象:
var btn = document.getElementById("myBtn");
var handler = function(){
alert("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
//这里省略了其他代码
EventUtil.removeHandler(btn, "click", handler);
13.3 事件对象
- 13.3.1 DOM中的事件对象
- 兼容 DOM 的浏览器会将一个 event 对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0 级或 DOM2 级) ,都会传入 event 对象。来看下面的例子。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.type); //"click"
};
btn.addEventListener("click", function(event){
alert(event.type); //"click"
}, false);
-
event 对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都会有下表列出的成员。
最新的属性和方法去w3c看;
在事件处理程序内部,对象 this 始终等于 currentTarget 的值,而 target 则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则 this、 currentTarget 和 target 包含相同的值。来看下面的例子。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.currentTarget === this); //true
alert(event.target === this); //true
};
- 这个例子检测了 currentTarget 和 target 与 this 的值。由于 click 事件的目标是按钮,因此这三个值是相等的。如果事件处理程序存在于按钮的父节点中(例如 document.body),那么这些值是不相同的。再看下面的例子。
document.body.onclick = function(event){
alert(event.currentTarget === document.body); //true
alert(this === document.body); //true
alert(event.target === document.getElementById("myBtn")); //true
};
- 在需要通过一个函数处理多个事件时,可以使用 type 属性。例如:
- 只有 cancelable 属性设置为 true 的事件,才可以使用 preventDefault()来取消其默认行为。
- 要阻止特定事件的默认行为,可以使用 preventDefault()方法。例如,链接的默认行为就是在被单击时会导航到其 href 特性指定的 URL。如果你想阻止链接导航这一默认行为,那么通过链接的onclick 事件处理程序可以取消它,如下面的例子所示:
var link = document.getElementById("myLink");
link.onclick = function(event){
event.preventDefault();
};
- 另外, stopPropagation()方法用于立即停止事件在 DOM 层次中的传播,即取消进一步的事件
捕获或冒泡。例如,直接添加到一个按钮的事件处理程序可以调用 stopPropagation(),从而避免触
发注册在 document.body 上面的事件处理程序,如下面的例子所示。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert("Clicked");
// 调用后,阻止向上冒泡 body注册的onclick就不会运行了。
event.stopPropagation();
};
document.body.onclick = function(event){
alert("Body clicked");
};
- 事件对象的 eventPhase 属性,可以用来确定事件当前正位于事件流的哪个阶段。如果是在捕获阶段调用的事件处理程序,那么 eventPhase 等于 1;如果事件处理程序处于目标对象上,则 eventPhase 等于 2;如果是在冒泡阶段调用的事件处理程序, eventPhase 等于 3。这里要注意的是,尽管“处于目标”发生在冒泡阶段,但 eventPhase 仍然一直等于 2。来看下面的例子。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.eventPhase); //2
};
document.body.addEventListener("click", function(event){
alert(event.eventPhase); //1
}, true);
document.body.onclick = function(event){
alert(event.eventPhase); //3
};
13.4 事件类型
- 13.4.1 UI事件
- UI 事件指的是那些不一定与用户操作有关的事件。这些事件在 DOM 规范出现之前,都是以这种或
那种形式存在的,而在 DOM 规范中保留是为了向后兼容。现有的 UI 事件如下。- load:当页面完全加载后在 window 上面触发,当所有框架都加载完毕时在框架集上面触发,
当图像加载完毕时在元素上面触发,或者当嵌入的内容加载完毕时在 - unload 事件:与 load 事件对应的是 unload 事件,这个事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生 unload 事件。而利用这个事件最多的情况是清除引用,以避免内存泄漏。
- resize 事件:当浏览器窗口被调整到一个新的高度或宽度时,就会触发 resize 事件。这个事件在 window(窗口)上面触发,因此可以通过 JavaScript 或者元素中的 onresize 特性来指定事件处理程序。
- load:当页面完全加载后在 window 上面触发,当所有框架都加载完毕时在框架集上面触发,
function myResize(event){
li.innerHTML = document.documentElement.clientWidth +" " + document.documentElement.clientHeight // 显示现在的窗口clientSize
}
document.body.onresize = myResize;
- scroll 事件:虽然 scroll 事件是在 window 对象上发生的,但它实际表示的则是页面中相应元素的变化。在混杂模式下,可以通过元素的 scrollLeft 和 scrollTop 来监控到这一变化;
- 13.4.2 焦点事件
- 焦点事件会在页面元素获得或失去焦点时触发。利用这些事件并与 document.hasFocus()方法及document.activeElement 属性配合,可以知晓用户在页面上的行踪。有以下 6 个焦点事件。
- blur:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
- focus:在元素获得焦点时触发。这个事件不会冒泡;所有浏览器都支持它
- focusin:在元素获得焦点时触发。这个事件与 HTML 事件 focus 等价,但它冒泡。
- focusout:在元素失去焦点时触发。这个事件是 HTML 事件 blur 的通用版本。
- 要确定浏览器是否支持这些事件,可以使用如下代码:
var isSupported = document.implementation.hasFeature("FocusEvent", "3.0");
13.4.3 鼠标与滚轮事件
要检测浏览器是否支持上面的所有事件,可以使用以下代码:
var isSupported = document.implementation.hasFeature("MouseEvent", "3.0")click:在用户单击主鼠标按钮(一般是左边的按钮)或者按下回车键时触发。这一点对确保
易访问性很重要,意味着 onclick 事件处理程序既可以通过键盘也可以通过鼠标执行。dblclick:在用户双击主鼠标按钮(一般是左边的按钮)时触发。从技术上说,这个事件并不
是 DOM2 级事件规范中规定的,但鉴于它得到了广泛支持,所以 DOM3 级事件将其纳入了标准。mousedown:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。
mouseenter:在鼠标光标从元素外部首次移动到元素范围之内时触发。这个事件不冒泡,而且
在光标移动到后代元素上不会触发。 DOM2 级事件并没有定义这个事件,但 DOM3 级事件将它
纳入了规范。 IE、 Firefox 9+和 Opera 支持这个事件。mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。这个事件不冒泡,而且
在光标移动到后代元素上不会触发。 DOM2 级事件并没有定义这个事件,但 DOM3 级事件将它
纳入了规范。 IE、 Firefox 9+和 Opera 支持这个事件。mousemove:当鼠标指针在元素内部移动时重复地触发。不能通过键盘触发这个事件。
mouseout:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。又移入的另
一个元素可能位于前一个元素的外部,也可能是这个元素的子元素。不能通过键盘触发这个事件。mouseover:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触
发。不能通过键盘触发这个事件。mouseup:在用户释放鼠标按钮时触发。不能通过键盘触发这个事件。
-
- 客户区坐标位置: 鼠标事件都是在浏览器视口中的特定位置上发生的。这个位置信息保存在事件对象的 clientX 和 clientY 属性中。
-
- 页面坐标位置: 通过事件对象的 pageX 和pageY 属性,能告诉你事件是在页面中的什么位置发生的。
-
- 屏幕坐标位置 : 通过 screenX 和 screenY 属性就可以确定鼠标事件发生时鼠标指针相对于整个屏幕的坐标信息。
function myCoor(event){
li.innerHTML = "client size = "+ event.clientX + " " + event.clientY + "
" +
" page size = " + event.pageX + " " + event.pageY + "
" +
" screen size = " + event.screenX + " " + event.screenY
}
- 修改键 : 虽然鼠标事件主要是使用鼠标来触发的,但在按下鼠标时键盘上的某些键的状态也可以影响到所要采取的操作。这些修改键就是 Shift、 Ctrl、 Alt 和 Meta(在 Windows 键盘中是 Windows 键,在苹果机中
是 Cmd 键),它们经常被用来修改鼠标事件的行为。 DOM 为此规定了 4 个属性,表示这些修改键的状
态: shiftKey、 ctrlKey、 altKey 和 metaKey。这些属性中包含的都是布尔值,如果相应的键被按
下了,则值为 true,否则值为 false。当某个鼠标事件发生时,通过检测这几个属性就可以确定用户
是否同时按下了其中的键。
function myClick(event){
li.innerHTML = "ctrl = " + String(event.ctrlKey) + " " + "shift =
" + String(event.shiftKey)+ " " + "microKey = " + String(event.metaKey)
}
- 相关元素 : 在发生 mouseover 和 mouserout 事件时,还会涉及更多的元素。这两个事件都会涉及把鼠标指
针从一个元素的边界之内移动到另一个元素的边界之内。对 mouseover 事件而言,事件的主目标是获得光标的元素,而相关元素(event.relatedTarget)就是那个失去光标的元素。类似地,对 mouseout 事件而言,事件的主目标是失去光标的元素,而相关元素(event.relatedTarget)则是获得光标的元素。来看下面的例子。
in DIV A
in DIV B
- 13.4.6 变动事件
(DOM3 级事件模块作废了很多变动事件, 建议改用MutationObserver构造函数代替。)- DOM2 级的变动(mutation)事件能在 DOM 中的某一部分发生变化时给出提示。
- DOMSubtreeModified:在 DOM 结构中发生任何变化时触发。这个事件在其他任何事件触发后都会触发。
- DOMNodeInserted:在一个节点作为子节点被插入到另一个节点中时触发。
- DOMNodeRemoved:在节点从其父节点中被移除时触发。
- DOMNodeInsertedIntoDocument:在一个节点被直接插入文档或通过子树间接插入文档之后
触发。这个事件在 DOMNodeInserted 之后触发。 - DOMNodeRemovedFromDocument:在一个节点被直接从文档中移除或通过子树间接从文档中移
除之前触发。这个事件在 DOMNodeRemoved 之后触发。 - DOMAttrModified:在特性被修改之后触发。
- DOMCharacterDataModified:在文本节点的值发生变化时触发。
- 使用下列代码可以检测出浏览器是否支持变动事件:
var isSupported = document.implementation.hasFeature("MutationEvents", "2.0");
-
1. 删除节点
- 在使用removeChild()或replaceChild()从 DOM中删除节点时,首先会触发DOMNodeRemoved事件。
- 如果被移除的节点包含子节点,那么在其所有子节点以及这个被移除的节点上会相继触发DOMNodeRemovedFromDocument 事件。但这个事件不会冒泡,所以只有直接指定给其中一个子节点的事件处理程序才会被调用。
- 紧随其后触发的是 DOMSubtreeModified 事件。这个事件的目标是被移除节点的父节点;
-
2. 插入节点
- 在使用 appendChild()、 replaceChild()或 insertBefore()向 DOM 中插入节点时,首先会触发 DOMNodeInserted 事件。
- 13.4.7 HTML5 事件
- 1. contextmenu 事件
- 用以表示何时应该显示上下文菜单,以便开发人员取消默认的上下文菜单而提供自定义的菜单。上下文菜单是指在用户交互(例如右键点击)时出现的菜单。 HTML5允许我们自定义此菜单。
Right click or Ctrl+click me to get a custom context menu. Click anywhere else to get the default context menu.
- 所有浏览器都支持 contextmenu 事件, contextmenu元素只有 Firefox 浏览器支持。
右击这里显示上下文菜单。
- 在所有浏览器中都可以取消这个事件:在兼容 DOM 的浏览器中,使用 event.preventDefalut();在 IE 中,将 event.returnValue 的值设置为 false。
- 2. beforeunload 事件
- 可以监听到浏览器关闭操作,能够在关闭之前,弹出一个对话框,让用户选择是否关闭。(只有IE支持?)
- 为了显示这个弹出对话框,必须将 event.returnValue 的值设置为要显示给用户的字符串(对
IE 及 Fiefox 而言),同时作为函数的值返回(对 Safari 和 Chrome 而言)(???Chrome和火狐不支持);
function myFunc(event){
var message = "I'm really going to miss you if you go.";
event.returnValue = message;
}
window.addEventListener("beforeunload", myFunc)
-
3. DOMContentLoaded 事件
- 在形成完整的 DOM 树之后就会触发,不理会图像、 JavaScript 文件、 CSS 文件或其他资源是否已经下载完毕。而 load 事件是在页面中的一切都加载完毕时触发。
- 可以为 document 或 window 添加 DOMContentLoaded 事件(尽管
这个事件会冒泡到 window,但它的目标实际上是 document)。 - 这个事件始终都会在 load 事件之前触发。
-
4. readystatechange 事件
- 提供与文档或元素的加载状态有关的信息。
- 当对象的readyState属性值发生变化时出发此事件。
- readyState属性值有:
- loading:加载(document 仍在加载。)
- interactive / 互动(文档已经完成加载,文档已被解析,但是诸如图像,样式表和框架之类的子资源仍在加载。)
- complete / 完成(T文档和所有子资源已完成加载。状态表示 load 事件即将被触发。 )
document.addEventListener("readystatechange", function(event){
alert("load completed")
})
- 对于 document 而言,值为"interactive"的 readyState 会在与 DOMContentLoaded 大致相同的时刻触发 readystatechange 事件。
// 以下类似使用DOMContentLoaded
document.addEventListener("readystatechange", function(event){
if (document.readyState == "interactive"){
alert("Content loaded");
}
})
- 交互阶段可能会早于也可能会晚于完成阶段出现,无法确保顺序。在包含
较多外部资源的页面中,交互阶段更有可能早于完成阶段出现;而在页面中包含较少外部资源的情况下,完成阶段先于交互阶段出现的可能性更大。因此,为了尽可能抢到先机,有必要同时检测交互和完成阶段。
document.addEventListener("readystatechange", function(event){
if (document.readyState == "interactive" || document.readyState == "complete"){
document.removeEventListener("readystatechange", arguments.callee);
alert("Content loaded");
}
})
另外,
-
如果可行的话,也可以考虑为 document 对象添加一个事件处理程序,用以处理页面上发生的某种特定类型的事件。
- document 对象很快就可以访问,而且可以在页面生命周期的任何时点上为它添加事件处理程序(无需等待 DOMContentLoaded 或 load 事件)。换句话说,只要可单击的元素呈现在页面上,就可以立即具备适当的功能。
- 在页面中设置事件处理程序所需的时间更少。只添加一个事件处理程序所需的 DOM 引用更少,
所花的时间也更少。 - 整个页面占用的内存空间更少,能够提升整体性能。
最适合采用事件委托技术的事件包括 click、mousedown、mouseup、keydown、keyup 和 keypress。
-
13.5.2 移除事件处理程序
- 使用 innerHTML 替换页面中某一部分的时候, 或使用 removeChild()和 replaceChild()方法时,内存中会留下那些过时不用的“空事件处理程序”(dangling event handler),也是造成 Web 应用程序内存与性能问题的主要原因。
- 如果你知道某个元素即将被移除,那么最好手工移除事件处理程序,如下面的例子所示。
导致“空事件处理程序”的另一种情况,就是卸载页面的时候。在页面卸载之前,先通过 onunload 事件处理程序移除所有事件处理程序。
13.6 模拟事件
13.6.1 DOM中的事件模拟
-
创建和分派DOM事件。这些事件通常称为合成事件,而不是浏览器本身触发的事件。
- 创建自定义事件: Events 可以使用构造函数创建;
//创建自定义事件 var event = new Event('click'); // 添加事件监听 elem.addEventListener('click', function (event) { do something.. }, false); // 派发事件 elem.dispatchEvent(event);
- 添加自定义数据 – CustomEvent()
- 使用CustomEvent接口的事件可用于携带自定义数据。
- IE浏览器不支持此方法,需要用 var event = document.createEvent('CustomEvent');
2018/11/7
-